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.
- package/build/charts/Legend.d.ts +8 -1
- package/build/charts/Legend.d.ts.map +1 -1
- package/build/charts/Legend.js +8 -4
- package/build/jsx-dev-runtime.d.ts +2 -1
- package/build/jsx-dev-runtime.d.ts.map +1 -1
- package/build/svg/Svg.d.ts +9 -0
- package/build/svg/Svg.d.ts.map +1 -1
- package/build/svg/Svg.js +9 -1
- package/build/ui/index.d.ts +3 -2
- package/build/ui/index.d.ts.map +1 -1
- package/build/ui/index.js +1 -1
- package/build/util/DOM.d.ts.map +1 -1
- package/build/util/DOM.js +16 -4
- package/build/util/getActiveElement.d.ts +1 -1
- package/build/util/getActiveElement.d.ts.map +1 -1
- package/build/util/getActiveElement.js +14 -2
- package/build/widgets/form/TextArea.d.ts +4 -0
- package/build/widgets/form/TextArea.d.ts.map +1 -1
- package/build/widgets/form/TextArea.js +7 -1
- package/build/widgets/grid/Grid.d.ts +3 -2
- package/build/widgets/grid/Grid.d.ts.map +1 -1
- package/build/widgets/nav/MenuItem.js +2 -2
- package/build/widgets/overlay/Dropdown.d.ts +4 -4
- package/build/widgets/overlay/Dropdown.d.ts.map +1 -1
- package/build/widgets/overlay/Dropdown.js +82 -38
- package/build/widgets/overlay/Overlay.d.ts.map +1 -1
- package/build/widgets/overlay/Overlay.js +7 -3
- package/build/widgets/overlay/Window.d.ts.map +1 -1
- package/build/widgets/overlay/Window.js +2 -1
- package/dist/charts.js +10 -3
- package/dist/manifest.js +789 -789
- package/dist/svg.js +13 -0
- package/dist/util.js +31 -9
- package/dist/widgets.js +139 -47
- package/package.json +1 -1
- package/src/charts/Legend.tsx +22 -2
- package/src/jsx-dev-runtime.ts +2 -1
- package/src/svg/Svg.tsx +24 -1
- package/src/ui/Cx.tsx +505 -505
- package/src/ui/DataProxy.ts +55 -55
- package/src/ui/Rescope.ts +50 -50
- package/src/ui/exprHelpers.ts +96 -96
- package/src/ui/index.ts +3 -6
- package/src/util/DOM.ts +15 -4
- package/src/util/getActiveElement.ts +16 -2
- package/src/widgets/Sandbox.ts +104 -104
- package/src/widgets/form/ColorField.scss +112 -112
- package/src/widgets/form/DateTimeField.scss +111 -111
- package/src/widgets/form/LookupField.maps.scss +26 -26
- package/src/widgets/form/MonthField.scss +113 -113
- package/src/widgets/form/Select.scss +104 -104
- package/src/widgets/form/TextArea.tsx +10 -1
- package/src/widgets/form/TextField.scss +66 -66
- package/src/widgets/grid/Grid.tsx +3 -2
- package/src/widgets/index.ts +41 -41
- package/src/widgets/nav/MenuItem.tsx +2 -2
- package/src/widgets/overlay/Dropdown.tsx +87 -27
- package/src/widgets/overlay/Overlay.tsx +5 -3
- 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
|
|
107
|
+
return getActiveElement() == el;
|
|
91
108
|
}
|
|
92
109
|
function isFocusedDeep(el) {
|
|
93
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
5937
|
-
|
|
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
|
|
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
|
-
|
|
6253
|
-
|
|
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.
|
|
6256
|
-
parentBounds = new DOMRect(
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 = `${
|
|
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 = `${
|
|
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 = `${
|
|
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 = `${
|
|
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 = `${
|
|
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 = `${
|
|
6393
|
-
style.bottom = `${
|
|
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,
|
|
6398
|
-
let h = Math.min(contentSize.height,
|
|
6399
|
-
style.top = `${Math.round((
|
|
6400
|
-
style.right = `${Math.round((
|
|
6401
|
-
style.bottom = `${Math.round((
|
|
6402
|
-
style.left = `${Math.round((
|
|
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(
|
|
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(
|
|
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:
|
|
6726
|
-
bottom:
|
|
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(
|
|
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 &&
|
|
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,
|
|
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
|
-
|
|
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
package/src/charts/Legend.tsx
CHANGED
|
@@ -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`,
|
package/src/jsx-dev-runtime.ts
CHANGED
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>
|