cx 26.8.0 → 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 (59) 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/jsx-dev-runtime.d.ts +2 -1
  5. package/build/jsx-dev-runtime.d.ts.map +1 -1
  6. package/build/svg/Svg.d.ts +9 -0
  7. package/build/svg/Svg.d.ts.map +1 -1
  8. package/build/svg/Svg.js +9 -1
  9. package/build/ui/index.d.ts +3 -2
  10. package/build/ui/index.d.ts.map +1 -1
  11. package/build/ui/index.js +1 -1
  12. package/build/util/DOM.d.ts.map +1 -1
  13. package/build/util/DOM.js +16 -4
  14. package/build/util/getActiveElement.d.ts +1 -1
  15. package/build/util/getActiveElement.d.ts.map +1 -1
  16. package/build/util/getActiveElement.js +14 -2
  17. package/build/widgets/form/TextArea.d.ts +4 -0
  18. package/build/widgets/form/TextArea.d.ts.map +1 -1
  19. package/build/widgets/form/TextArea.js +7 -1
  20. package/build/widgets/grid/Grid.d.ts +3 -2
  21. package/build/widgets/grid/Grid.d.ts.map +1 -1
  22. package/build/widgets/nav/MenuItem.js +2 -2
  23. package/build/widgets/overlay/Dropdown.d.ts +4 -4
  24. package/build/widgets/overlay/Dropdown.d.ts.map +1 -1
  25. package/build/widgets/overlay/Dropdown.js +82 -38
  26. package/build/widgets/overlay/Overlay.d.ts.map +1 -1
  27. package/build/widgets/overlay/Overlay.js +7 -3
  28. package/build/widgets/overlay/Window.d.ts.map +1 -1
  29. package/build/widgets/overlay/Window.js +2 -1
  30. package/dist/charts.js +10 -3
  31. package/dist/manifest.js +789 -789
  32. package/dist/svg.js +13 -0
  33. package/dist/util.js +31 -9
  34. package/dist/widgets.js +139 -47
  35. package/package.json +1 -1
  36. package/src/charts/Legend.tsx +22 -2
  37. package/src/jsx-dev-runtime.ts +2 -1
  38. package/src/svg/Svg.tsx +24 -1
  39. package/src/ui/Cx.tsx +505 -505
  40. package/src/ui/DataProxy.ts +55 -55
  41. package/src/ui/Rescope.ts +50 -50
  42. package/src/ui/exprHelpers.ts +96 -96
  43. package/src/ui/index.ts +3 -6
  44. package/src/util/DOM.ts +15 -4
  45. package/src/util/getActiveElement.ts +16 -2
  46. package/src/widgets/Sandbox.ts +104 -104
  47. package/src/widgets/form/ColorField.scss +112 -112
  48. package/src/widgets/form/DateTimeField.scss +111 -111
  49. package/src/widgets/form/LookupField.maps.scss +26 -26
  50. package/src/widgets/form/MonthField.scss +113 -113
  51. package/src/widgets/form/Select.scss +104 -104
  52. package/src/widgets/form/TextArea.tsx +10 -1
  53. package/src/widgets/form/TextField.scss +66 -66
  54. package/src/widgets/grid/Grid.tsx +3 -2
  55. package/src/widgets/index.ts +41 -41
  56. package/src/widgets/nav/MenuItem.tsx +2 -2
  57. package/src/widgets/overlay/Dropdown.tsx +87 -27
  58. package/src/widgets/overlay/Overlay.tsx +5 -3
  59. package/src/widgets/overlay/Window.tsx +2 -1
package/dist/svg.js CHANGED
@@ -234,6 +234,15 @@ class Svg extends BoundedObject {
234
234
  constructor(config) {
235
235
  super(config);
236
236
  }
237
+ declareData(...args) {
238
+ return super.declareData(
239
+ {
240
+ role: undefined,
241
+ ariaLabel: undefined,
242
+ },
243
+ ...args,
244
+ );
245
+ }
237
246
  initState(context, instance) {
238
247
  const size = {
239
248
  width: 0,
@@ -308,6 +317,8 @@ class SvgComponent extends VDOM.Component {
308
317
  render() {
309
318
  const { instance, data, size, children, eventHandlers } = this.props;
310
319
  const { widget } = instance;
320
+ // An unnamed drawing is left alone: `role="img"` would hide its text and put nothing in its place.
321
+ const role = data.role ?? (data.ariaLabel ? "img" : undefined);
311
322
  const defs = [];
312
323
  for (const k in instance.clipRects) {
313
324
  let cr = instance.clipRects[k];
@@ -350,6 +361,8 @@ class SvgComponent extends VDOM.Component {
350
361
  size.width > 0 &&
351
362
  size.height > 0 &&
352
363
  jsxs("svg", {
364
+ role: role,
365
+ "aria-label": data.ariaLabel,
353
366
  children: [
354
367
  jsx("defs", {
355
368
  children: defs,
package/dist/util.js CHANGED
@@ -57,6 +57,23 @@ function isNumber(x) {
57
57
  return typeof x === "number";
58
58
  }
59
59
 
60
+ //IE sometimes returns null while other browsers always return document.body.
61
+ function getActiveElement(doc = document) {
62
+ let active = doc.activeElement ?? doc.body;
63
+ //when focus is inside a same-origin iframe, `doc.activeElement` only reports the
64
+ //<iframe> element itself - drill into its own document to find the element that's
65
+ //actually focused there (recursively, in case of nested iframes)
66
+ if (active && active.tagName === "IFRAME") {
67
+ const iframe = active;
68
+ let frameDoc = iframe.contentDocument;
69
+ if (frameDoc) {
70
+ const inner = getActiveElement(frameDoc);
71
+ return inner ?? active;
72
+ }
73
+ }
74
+ return active;
75
+ }
76
+
60
77
  function findFirst(el, condition) {
61
78
  if (condition(el)) return el;
62
79
  var children = el.children;
@@ -87,10 +104,10 @@ function closestParent(el, condition) {
87
104
  return el && closest(el.parentElement, condition);
88
105
  }
89
106
  function isFocused(el) {
90
- return document.activeElement == el;
107
+ return getActiveElement() == el;
91
108
  }
92
109
  function isFocusedDeep(el) {
93
- return document.activeElement == el || (!!document.activeElement && el.contains(document.activeElement));
110
+ return isSelfOrDescendant(el, getActiveElement());
94
111
  }
95
112
  const focusableWithoutTabIndex = ["INPUT", "SELECT", "TEXTAREA", "A", "BUTTON"];
96
113
  function isFocusable(el) {
@@ -105,13 +122,23 @@ function isFocusable(el) {
105
122
  * @returns {Element}
106
123
  */
107
124
  function getFocusedElement() {
108
- return document.activeElement;
125
+ return getActiveElement();
109
126
  }
110
127
  function isDescendant(el, descEl) {
111
128
  return el.contains(descEl);
112
129
  }
130
+ //`descEl` may live in a different document than `el` (e.g. focus is inside a same-origin
131
+ //iframe) - Node.contains never crosses document boundaries, so walk out through each
132
+ //frame's <iframe> element in its parent document until we either cross into el's document
133
+ //or run out of ancestor frames.
113
134
  function isSelfOrDescendant(el, descEl) {
114
- return el == descEl || el.contains(descEl);
135
+ let node = descEl;
136
+ while (node) {
137
+ if (el == node || el.contains(node)) return true;
138
+ let win = node.ownerDocument?.defaultView;
139
+ node = win?.frameElement;
140
+ }
141
+ return false;
115
142
  }
116
143
 
117
144
  var globalCacheIdentifier = 1;
@@ -1344,11 +1371,6 @@ function reverseSlice(array, start) {
1344
1371
  }
1345
1372
  }
1346
1373
 
1347
- //IE sometimes returns null while other browsers always return document.body.
1348
- function getActiveElement() {
1349
- return document.activeElement ?? document.body;
1350
- }
1351
-
1352
1374
  /**
1353
1375
  * Type-checks a CreateConfig object based on its `type` or `$type` property.
1354
1376
  * This is a compile-time helper that returns the input unchanged at runtime.
package/dist/widgets.js CHANGED
@@ -5933,9 +5933,14 @@ class OverlayComponent extends VDOM$2.Component {
5933
5933
  widget.handleResize(e, instance, this);
5934
5934
  } else {
5935
5935
  let prefix = this.getResizePrefix(e);
5936
- this.setCustomStyle({
5937
- cursor: prefix ? prefix + "-resize" : undefined,
5938
- });
5936
+ if (prefix)
5937
+ this.setCustomStyle({
5938
+ cursor: `${prefix}-resize`,
5939
+ });
5940
+ else if (this.customStyle.cursor) {
5941
+ delete this.customStyle.cursor;
5942
+ if (this.el) this.el.style.cursor = data.style?.cursor ?? "";
5943
+ }
5939
5944
  }
5940
5945
  }
5941
5946
  startMoveOperation(e) {
@@ -6209,6 +6214,21 @@ class DropdownBase extends OverlayBase {
6209
6214
  el.addEventListener("scroll", component.updateDropdownPosition);
6210
6215
  });
6211
6216
  component.offResize = ResizeManager.subscribe(component.updateDropdownPosition);
6217
+ //if the related element lives in a different document (e.g. inside a same-origin
6218
+ //iframe), that document/window has its own scroll position and its own resize event
6219
+ //(which also fires when the <iframe> element itself is resized) - neither is observed
6220
+ //by the top-level `window` above or by the relatedElement's parentElement chain, so
6221
+ //track every frame window between the related element's document and the top document.
6222
+ var frameWindows = (component.frameWindows = []);
6223
+ var frameWin = instance.relatedElement?.ownerDocument?.defaultView;
6224
+ while (frameWin && frameWin !== window) {
6225
+ frameWindows.push(frameWin);
6226
+ frameWin = frameWin.frameElement?.ownerDocument?.defaultView;
6227
+ }
6228
+ frameWindows.forEach((w) => {
6229
+ w.addEventListener("scroll", component.updateDropdownPosition);
6230
+ w.addEventListener("resize", component.updateDropdownPosition);
6231
+ });
6212
6232
  if (this.onDropdownDidMount) instance.invoke("onDropdownDidMount", instance, component);
6213
6233
  if (this.pipeValidateDropdownPosition)
6214
6234
  instance.invoke("pipeValidateDropdownPosition", component.updateDropdownPosition, instance);
@@ -6221,14 +6241,21 @@ class DropdownBase extends OverlayBase {
6221
6241
  this.updateDropdownPosition(instance, component);
6222
6242
  }
6223
6243
  overlayWillUnmount(instance, component) {
6224
- var { scrollableParents } = component;
6244
+ var { scrollableParents, frameWindows } = component;
6225
6245
  if (scrollableParents) {
6226
6246
  scrollableParents.forEach((el) => {
6227
6247
  el.removeEventListener("scroll", component.updateDropdownPosition);
6228
6248
  });
6229
6249
  delete component.scrollableParents;
6230
- delete component.updateDropdownPosition;
6231
6250
  }
6251
+ if (frameWindows) {
6252
+ frameWindows.forEach((w) => {
6253
+ w.removeEventListener("scroll", component.updateDropdownPosition);
6254
+ w.removeEventListener("resize", component.updateDropdownPosition);
6255
+ });
6256
+ delete component.frameWindows;
6257
+ }
6258
+ delete component.updateDropdownPosition;
6232
6259
  if (component.offResize) component.offResize();
6233
6260
  if (this.pipeValidateDropdownPosition) instance.invoke("pipeValidateDropdownPosition", null, instance);
6234
6261
  if (component.offParentPositionChange) component.offParentPositionChange();
@@ -6243,22 +6270,38 @@ class DropdownBase extends OverlayBase {
6243
6270
  updateDropdownPosition(instance, component) {
6244
6271
  var { el, initialScreenPosition } = component;
6245
6272
  var { data, relatedElement } = instance;
6246
- var parentBounds = getTopLevelBoundingClientRect(relatedElement);
6273
+ var elDocument = el.ownerDocument || document;
6274
+ //if the dropdown renders in the same document as its related element (e.g. not
6275
+ //portaled to the top-level body, such as an inline dropdown living inside an
6276
+ //iframe together with its field), no cross-document offset should be applied -
6277
+ //getBoundingClientRect() is already relative to that shared document/viewport.
6278
+ var isSameDocument = el && relatedElement && el.ownerDocument === relatedElement.ownerDocument;
6279
+ var parentBounds = isSameDocument
6280
+ ? relatedElement.getBoundingClientRect()
6281
+ : getTopLevelBoundingClientRect(relatedElement);
6247
6282
  //getBoundingClientRect() will return an empty rect if the element is hidden or removed
6248
6283
  if (parentBounds.left == 0 && parentBounds.top == 0 && parentBounds.bottom == 0 && parentBounds.right == 0) {
6249
6284
  if (!component.parentBounds) return;
6250
6285
  parentBounds = component.parentBounds;
6251
6286
  } else component.parentBounds = parentBounds;
6252
- if (this.trackMouseX && instance.mousePosition) {
6253
- parentBounds = new DOMRect(instance.mousePosition.x, parentBounds.top, 0, parentBounds.bottom - parentBounds.top);
6287
+ //instance.mousePosition (set from getCursorPos) is always expressed in top-document
6288
+ //coordinates. When the dropdown and its related element share a document that isn't
6289
+ //the top document (e.g. both live inside the same iframe), parentBounds above is in
6290
+ //that document's own coordinate space, so the mouse position must be translated into
6291
+ //the same space before it can replace part of parentBounds.
6292
+ var mousePosition = instance.mousePosition;
6293
+ if (mousePosition && isSameDocument && elDocument !== document) {
6294
+ const frameOffset = getParentFrameBoundingClientRect(relatedElement);
6295
+ mousePosition = {
6296
+ x: mousePosition.x - frameOffset.left,
6297
+ y: mousePosition.y - frameOffset.top,
6298
+ };
6254
6299
  }
6255
- if (this.trackMouseY && instance.mousePosition) {
6256
- parentBounds = new DOMRect(
6257
- parentBounds.left,
6258
- instance.mousePosition.y,
6259
- parentBounds.right - parentBounds.left,
6260
- 0,
6261
- );
6300
+ if (this.trackMouseX && mousePosition) {
6301
+ parentBounds = new DOMRect(mousePosition.x, parentBounds.top, 0, parentBounds.bottom - parentBounds.top);
6302
+ }
6303
+ if (this.trackMouseY && mousePosition) {
6304
+ parentBounds = new DOMRect(parentBounds.left, mousePosition.y, parentBounds.right - parentBounds.left, 0);
6262
6305
  }
6263
6306
  let explode = this.pad && typeof this.elementExplode === "number" ? this.elementExplode : 0;
6264
6307
  if (explode) {
@@ -6273,9 +6316,24 @@ class DropdownBase extends OverlayBase {
6273
6316
  if (this.matchWidth) style.minWidth = `${parentBounds.right - parentBounds.left}px`;
6274
6317
  if (this.matchMaxWidth) style.maxWidth = `${parentBounds.right - parentBounds.left}px`;
6275
6318
  var contentSize = this.measureNaturalDropdownSize(instance, component);
6276
- var placement = this.findOptimalPlacement(contentSize, parentBounds, data.placement, component.lastPlacement);
6319
+ var placement = this.findOptimalPlacement(
6320
+ contentSize,
6321
+ parentBounds,
6322
+ data.placement,
6323
+ component.lastPlacement,
6324
+ elDocument,
6325
+ );
6277
6326
  var arrowAdjust = this.getArrowAdjust(component);
6278
- this.applyPositioningPlacementStyles(style, placement, contentSize, parentBounds, el, false, arrowAdjust);
6327
+ this.applyPositioningPlacementStyles(
6328
+ style,
6329
+ placement,
6330
+ contentSize,
6331
+ parentBounds,
6332
+ el,
6333
+ false,
6334
+ arrowAdjust,
6335
+ elDocument,
6336
+ );
6279
6337
  component.setCustomStyle(style);
6280
6338
  this.setDirectionClass(component, placement);
6281
6339
  if (this.constrain) {
@@ -6283,7 +6341,16 @@ class DropdownBase extends OverlayBase {
6283
6341
  let newContentSize = this.measureNaturalDropdownSize(instance, component);
6284
6342
  if (newContentSize.width != contentSize.width || newContentSize.height != contentSize.height) {
6285
6343
  let newStyle = {};
6286
- this.applyPositioningPlacementStyles(newStyle, placement, newContentSize, parentBounds, el, true, arrowAdjust);
6344
+ this.applyPositioningPlacementStyles(
6345
+ newStyle,
6346
+ placement,
6347
+ newContentSize,
6348
+ parentBounds,
6349
+ el,
6350
+ true,
6351
+ arrowAdjust,
6352
+ elDocument,
6353
+ );
6287
6354
  component.setCustomStyle(newStyle);
6288
6355
  }
6289
6356
  }
@@ -6312,8 +6379,8 @@ class DropdownBase extends OverlayBase {
6312
6379
  component.cachedArrowOffset = parsed;
6313
6380
  return parsed;
6314
6381
  }
6315
- applyFixedPositioningPlacementStyles(style, placement, contentSize, rel, el, noAuto, arrowAdjust = 0) {
6316
- let viewport = getViewportRect(this.screenPadding);
6382
+ applyFixedPositioningPlacementStyles(style, placement, contentSize, rel, el, noAuto, arrowAdjust = 0, elDocument) {
6383
+ let viewport = getViewportRect(this.screenPadding, elDocument);
6317
6384
  style.position = "fixed";
6318
6385
  if (placement.startsWith("down")) {
6319
6386
  style.top = `${(this.cover ? rel.top : rel.bottom) + this.offset}px`;
@@ -6326,7 +6393,7 @@ class DropdownBase extends OverlayBase {
6326
6393
  let top = rel.top - this.offset - contentSize.height - viewport.top;
6327
6394
  style.top =
6328
6395
  this.constrain && (noAuto || top < this.screenPadding + 10) ? Math.max(this.screenPadding, top) + "px" : "auto";
6329
- style.bottom = document.documentElement.offsetHeight - (this.cover ? rel.bottom : rel.top) + this.offset + "px";
6396
+ style.bottom = elDocument.documentElement.clientHeight - (this.cover ? rel.bottom : rel.top) + this.offset + "px";
6330
6397
  }
6331
6398
  switch (placement) {
6332
6399
  case "down":
@@ -6339,7 +6406,7 @@ class DropdownBase extends OverlayBase {
6339
6406
  style.left = `${rel.left - arrowAdjust}px`;
6340
6407
  break;
6341
6408
  case "down-left":
6342
- style.right = `${document.documentElement.offsetWidth - rel.right - arrowAdjust}px`;
6409
+ style.right = `${elDocument.documentElement.clientWidth - rel.right - arrowAdjust}px`;
6343
6410
  style.left = "auto";
6344
6411
  break;
6345
6412
  case "up":
@@ -6352,7 +6419,7 @@ class DropdownBase extends OverlayBase {
6352
6419
  style.left = `${rel.left - arrowAdjust}px`;
6353
6420
  break;
6354
6421
  case "up-left":
6355
- style.right = `${document.documentElement.offsetWidth - rel.right - arrowAdjust}px`;
6422
+ style.right = `${elDocument.documentElement.clientWidth - rel.right - arrowAdjust}px`;
6356
6423
  style.left = "auto";
6357
6424
  break;
6358
6425
  case "right":
@@ -6371,40 +6438,40 @@ class DropdownBase extends OverlayBase {
6371
6438
  case "right-up":
6372
6439
  style.top = "auto";
6373
6440
  style.right = "auto";
6374
- style.bottom = `${document.documentElement.offsetHeight - rel.bottom - arrowAdjust}px`;
6441
+ style.bottom = `${elDocument.documentElement.clientHeight - rel.bottom - arrowAdjust}px`;
6375
6442
  style.left = `${rel.right + this.offset}px`;
6376
6443
  break;
6377
6444
  case "left":
6378
6445
  case "left-center":
6379
6446
  style.top = `${Math.round((rel.top + rel.bottom - el.offsetHeight) / 2)}px`;
6380
- style.right = `${document.documentElement.offsetWidth - rel.left + this.offset}px`;
6447
+ style.right = `${elDocument.documentElement.clientWidth - rel.left + this.offset}px`;
6381
6448
  style.bottom = "auto";
6382
6449
  style.left = "auto";
6383
6450
  break;
6384
6451
  case "left-down":
6385
6452
  style.top = `${rel.top - arrowAdjust}px`;
6386
- style.right = `${document.documentElement.offsetWidth - rel.left + this.offset}px`;
6453
+ style.right = `${elDocument.documentElement.clientWidth - rel.left + this.offset}px`;
6387
6454
  style.bottom = "auto";
6388
6455
  style.left = "auto";
6389
6456
  break;
6390
6457
  case "left-up":
6391
6458
  style.top = "auto";
6392
- style.right = `${document.documentElement.offsetWidth - rel.left + this.offset}px`;
6393
- style.bottom = `${document.documentElement.offsetHeight - rel.bottom - arrowAdjust}px`;
6459
+ style.right = `${elDocument.documentElement.clientWidth - rel.left + this.offset}px`;
6460
+ style.bottom = `${elDocument.documentElement.clientHeight - rel.bottom - arrowAdjust}px`;
6394
6461
  style.left = "auto";
6395
6462
  break;
6396
6463
  case "screen-center":
6397
- let w = Math.min(contentSize.width, document.documentElement.offsetWidth - 2 * this.screenPadding);
6398
- let h = Math.min(contentSize.height, document.documentElement.offsetHeight - 2 * this.screenPadding);
6399
- style.top = `${Math.round((document.documentElement.offsetHeight - h) / 2)}px`;
6400
- style.right = `${Math.round((document.documentElement.offsetWidth - w) / 2)}px`;
6401
- style.bottom = `${Math.round((document.documentElement.offsetHeight - h) / 2)}px`;
6402
- style.left = `${Math.round((document.documentElement.offsetWidth - w) / 2)}px`;
6464
+ let w = Math.min(contentSize.width, elDocument.documentElement.clientWidth - 2 * this.screenPadding);
6465
+ let h = Math.min(contentSize.height, elDocument.documentElement.clientHeight - 2 * this.screenPadding);
6466
+ style.top = `${Math.round((elDocument.documentElement.clientHeight - h) / 2)}px`;
6467
+ style.right = `${Math.round((elDocument.documentElement.clientWidth - w) / 2)}px`;
6468
+ style.bottom = `${Math.round((elDocument.documentElement.clientHeight - h) / 2)}px`;
6469
+ style.left = `${Math.round((elDocument.documentElement.clientWidth - w) / 2)}px`;
6403
6470
  break;
6404
6471
  }
6405
6472
  }
6406
- applyAbsolutePositioningPlacementStyles(style, placement, contentSize, rel, el, noAuto, arrowAdjust = 0) {
6407
- var viewport = getViewportRect(this.screenPadding);
6473
+ applyAbsolutePositioningPlacementStyles(style, placement, contentSize, rel, el, noAuto, arrowAdjust = 0, elDocument) {
6474
+ var viewport = getViewportRect(this.screenPadding, elDocument);
6408
6475
  style.position = "absolute";
6409
6476
  if (placement.startsWith("down")) {
6410
6477
  style.top = `${rel.bottom - rel.top + this.offset}px`;
@@ -6488,7 +6555,16 @@ class DropdownBase extends OverlayBase {
6488
6555
  break;
6489
6556
  }
6490
6557
  }
6491
- applyPositioningPlacementStyles(style, placement, contentSize, parentBounds, el, noAuto, arrowAdjust = 0) {
6558
+ applyPositioningPlacementStyles(
6559
+ style,
6560
+ placement,
6561
+ contentSize,
6562
+ parentBounds,
6563
+ el,
6564
+ noAuto,
6565
+ arrowAdjust = 0,
6566
+ elDocument,
6567
+ ) {
6492
6568
  switch (this.positioning) {
6493
6569
  case "absolute":
6494
6570
  this.applyAbsolutePositioningPlacementStyles(
@@ -6499,6 +6575,7 @@ class DropdownBase extends OverlayBase {
6499
6575
  el,
6500
6576
  noAuto,
6501
6577
  arrowAdjust,
6578
+ elDocument,
6502
6579
  );
6503
6580
  break;
6504
6581
  case "auto":
@@ -6511,6 +6588,7 @@ class DropdownBase extends OverlayBase {
6511
6588
  el,
6512
6589
  noAuto,
6513
6590
  arrowAdjust,
6591
+ elDocument,
6514
6592
  );
6515
6593
  else
6516
6594
  this.applyFixedPositioningPlacementStyles(
@@ -6521,10 +6599,20 @@ class DropdownBase extends OverlayBase {
6521
6599
  el,
6522
6600
  noAuto,
6523
6601
  arrowAdjust,
6602
+ elDocument,
6524
6603
  );
6525
6604
  break;
6526
6605
  default:
6527
- this.applyFixedPositioningPlacementStyles(style, placement, contentSize, parentBounds, el, noAuto, arrowAdjust);
6606
+ this.applyFixedPositioningPlacementStyles(
6607
+ style,
6608
+ placement,
6609
+ contentSize,
6610
+ parentBounds,
6611
+ el,
6612
+ noAuto,
6613
+ arrowAdjust,
6614
+ elDocument,
6615
+ );
6528
6616
  break;
6529
6617
  }
6530
6618
  }
@@ -6559,12 +6647,12 @@ class DropdownBase extends OverlayBase {
6559
6647
  }
6560
6648
  return size;
6561
6649
  }
6562
- findOptimalPlacement(contentSize, target, placement, lastPlacement) {
6650
+ findOptimalPlacement(contentSize, target, placement, lastPlacement, elDocument) {
6563
6651
  var placementOrder = this.placementOrder.split(" ");
6564
6652
  var best = lastPlacement || placement;
6565
6653
  var first;
6566
6654
  var score = {};
6567
- var viewport = getViewportRect();
6655
+ var viewport = getViewportRect(0, elDocument);
6568
6656
  for (var i = 0; i < placementOrder.length; i++) {
6569
6657
  var p = placementOrder[i];
6570
6658
  if (!first) first = p;
@@ -6718,12 +6806,12 @@ DropdownBase.prototype.cover = false;
6718
6806
  class Dropdown extends DropdownBase {}
6719
6807
  Widget$1.alias("dropdown", Dropdown);
6720
6808
  Localization$1.registerPrototype("cx/widgets/Dropdown", Dropdown);
6721
- function getViewportRect(padding = 0) {
6809
+ function getViewportRect(padding = 0, elDocument = document) {
6722
6810
  return {
6723
6811
  left: padding,
6724
6812
  top: padding,
6725
- right: document.documentElement.offsetWidth - padding,
6726
- bottom: document.documentElement.offsetHeight - padding,
6813
+ right: elDocument.documentElement.clientWidth - padding,
6814
+ bottom: elDocument.documentElement.clientHeight - padding,
6727
6815
  };
6728
6816
  }
6729
6817
 
@@ -7199,7 +7287,7 @@ class WindowComponent extends OverlayComponent {
7199
7287
  onFocusIn() {
7200
7288
  super.onFocusIn();
7201
7289
  if (!this.state.active) {
7202
- if (this.containerEl?.contains(document.activeElement)) this.setZIndex(ZIndexManager.next());
7290
+ if (this.containerEl?.contains(getActiveElement())) this.setZIndex(ZIndexManager.next());
7203
7291
  this.setState({
7204
7292
  active: true,
7205
7293
  });
@@ -8540,7 +8628,7 @@ let MenuItemComponent$1 = class MenuItemComponent extends VDOM$2.Component {
8540
8628
  if (widget.dropdown) {
8541
8629
  debug(menuFlag, "MenuItem", "mouseLeave", this.el);
8542
8630
  this.clearAutoFocusTimer();
8543
- if (widget.hoverToOpen && document.activeElement == this.el) unfocusElement(this.el);
8631
+ if (widget.hoverToOpen && getActiveElement() == this.el) unfocusElement(this.el);
8544
8632
  }
8545
8633
  tooltipMouseLeave$1(e, this.props.instance, widget.tooltip);
8546
8634
  }
@@ -8628,7 +8716,7 @@ let MenuItemComponent$1 = class MenuItemComponent extends VDOM$2.Component {
8628
8716
  let { widget } = this.props.instance;
8629
8717
  if (widget.dropdown) {
8630
8718
  oneFocusOut(this, this.el, this.onFocusOut.bind(this));
8631
- debug(menuFlag, "MenuItem", "focus", this.el, document.activeElement);
8719
+ debug(menuFlag, "MenuItem", "focus", this.el, getActiveElement());
8632
8720
  this.clearAutoFocusTimer();
8633
8721
  if (widget.openOnFocus) this.openDropdown();
8634
8722
  }
@@ -13000,8 +13088,12 @@ class Input extends VDOM$2.Component {
13000
13088
  });
13001
13089
  }
13002
13090
  if (instance.widget.reactOn.indexOf(change) != -1) {
13003
- let value = inputValue || widget.emptyValue;
13091
+ // Trimming on `input` would strip whitespace the user is still typing, so a trailing
13092
+ // newline could never be entered.
13093
+ let text = change != "input" && data.trim && isString(inputValue) ? inputValue.trim() : inputValue;
13094
+ let value = text || widget.emptyValue;
13004
13095
  instance.set("value", value);
13096
+ if (this.input && text != inputValue) this.input.value = text;
13005
13097
  }
13006
13098
  }
13007
13099
  onFocus() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cx",
3
- "version": "26.8.0",
3
+ "version": "26.9.0",
4
4
  "description": "Advanced JavaScript UI framework for admin and dashboard applications with ready to use grid, form and chart components.",
5
5
  "exports": {
6
6
  "./data": {
@@ -41,6 +41,15 @@ export interface LegendConfig extends HtmlElementConfig {
41
41
  /** Default shape to use for all entries. */
42
42
  shape?: StringProp;
43
43
 
44
+ /** ARIA role for each entry's shape `svg`. Defaults to `img` when `shapeAriaLabel` is set. */
45
+ shapeRole?: StringProp;
46
+
47
+ /**
48
+ * Accessible name for each entry's shape `svg`. Unnamed shapes are hidden from assistive
49
+ * technology, since the entry text already says what they mean.
50
+ */
51
+ shapeAriaLabel?: StringProp;
52
+
44
53
  /** Style applied to each entry. */
45
54
  entryStyle?: StyleProp;
46
55
 
@@ -97,6 +106,8 @@ export class Legend extends HtmlElement {
97
106
  declareData(...args: any[]): void {
98
107
  super.declareData(...args, {
99
108
  shape: undefined,
109
+ shapeRole: undefined,
110
+ shapeAriaLabel: undefined,
100
111
  entryStyle: { structured: true },
101
112
  entryClass: { structured: true },
102
113
  valueStyle: { structured: true },
@@ -124,6 +135,8 @@ export class Legend extends HtmlElement {
124
135
  case "shapeSize":
125
136
  case "svgSize":
126
137
  case "shape":
138
+ case "shapeRole":
139
+ case "shapeAriaLabel":
127
140
  case "entryStyle":
128
141
  case "entryClass":
129
142
  case "valueStyle":
@@ -177,7 +190,7 @@ export class Legend extends HtmlElement {
177
190
  instance.legends[this.name] && instance.legends[this.name].entries,
178
191
  list: React.ReactNode;
179
192
 
180
- let { entryClass, entryStyle, shape, valueClass, valueStyle } =
193
+ let { entryClass, entryStyle, shape, shapeRole, shapeAriaLabel, valueClass, valueStyle } =
181
194
  instance.data;
182
195
  let valueFormatter = Format.parse(this.valueFormat);
183
196
 
@@ -210,7 +223,7 @@ export class Legend extends HtmlElement {
210
223
  onMouseMove={onMouseMove}
211
224
  onMouseLeave={onMouseLeave}
212
225
  >
213
- {this.renderShape(e, shape)}
226
+ {this.renderShape(e, shape, shapeRole, shapeAriaLabel)}
214
227
  <div className={entryTextClass}>{e.displayText || e.name}</div>
215
228
  {this.showValues && (
216
229
  <div className={valueClasses} style={valueStyle}>
@@ -229,6 +242,8 @@ export class Legend extends HtmlElement {
229
242
  renderShape(
230
243
  entry: LegendEntryData,
231
244
  legendEntriesShape: string | null | undefined,
245
+ role?: string,
246
+ ariaLabel?: string,
232
247
  ): React.ReactNode {
233
248
  const className = this.CSS.element(this.baseClass, "shape", {
234
249
  [`color-${entry.colorIndex}`]:
@@ -244,6 +259,11 @@ export class Legend extends HtmlElement {
244
259
  return (
245
260
  <svg
246
261
  className={this.CSS.element(this.baseClass, "svg")}
262
+ role={role ?? (ariaLabel ? "img" : undefined)}
263
+ aria-label={ariaLabel}
264
+ // Mutually exclusive with the above: `aria-hidden` removes the element from the
265
+ // accessibility tree, which is also what a tagged PDF is built from.
266
+ aria-hidden={role == null && !ariaLabel ? true : undefined}
247
267
  style={{
248
268
  width: `${this.svgSize}px`,
249
269
  height: `${this.svgSize}px`,
@@ -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>