cx 26.9.1 → 26.9.3

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.
@@ -0,0 +1,50 @@
1
+ import { createModel } from "../../data/createAccessorModelProxy";
2
+ import { Store } from "../../data/Store";
3
+ import { createTestRenderer } from "../../util/test/createTestRenderer";
4
+ import { TextField } from "./TextField";
5
+ import { LabeledContainer } from "./LabeledContainer";
6
+ import assert from "assert";
7
+
8
+ interface Model {
9
+ name: string;
10
+ caption: string;
11
+ hint: string;
12
+ }
13
+
14
+ function collectText(node: any): string {
15
+ if (node == null) return "";
16
+ if (typeof node === "string") return node;
17
+ if (Array.isArray(node)) return node.map(collectText).join("");
18
+ return collectText(node.children);
19
+ }
20
+
21
+ describe("Field", () => {
22
+ const m = createModel<Model>();
23
+
24
+ it("accepts an accessor chain as label", async () => {
25
+ let store = new Store({ data: { caption: "Full name" } });
26
+ let component = await createTestRenderer(store, <TextField value={m.name} label={m.caption} />);
27
+ assert.ok(collectText(component.toJSON()).includes("Full name"));
28
+ });
29
+
30
+ it("accepts an accessor chain as help", async () => {
31
+ let store = new Store({ data: { hint: "As on your passport" } });
32
+ let component = await createTestRenderer(store, <TextField value={m.name} help={m.hint} />);
33
+ assert.ok(collectText(component.toJSON()).includes("As on your passport"));
34
+ });
35
+ });
36
+
37
+ describe("LabeledContainer", () => {
38
+ const m = createModel<Model>();
39
+
40
+ it("accepts an accessor chain as label", async () => {
41
+ let store = new Store({ data: { caption: "Contact" } });
42
+ let component = await createTestRenderer(
43
+ store,
44
+ <LabeledContainer label={m.caption}>
45
+ <TextField value={m.name} />
46
+ </LabeledContainer>,
47
+ );
48
+ assert.ok(collectText(component.toJSON()).includes("Contact"));
49
+ });
50
+ });
@@ -252,7 +252,7 @@ export class Field<
252
252
  if (this.help != null) {
253
253
  let helpConfig: any = {};
254
254
 
255
- if ((this.help as any).isComponentType) helpConfig = this.help;
255
+ if ((this.help as any).isComponentType === true) helpConfig = this.help;
256
256
  else if (isSelector(this.help)) helpConfig.text = this.help;
257
257
  else Object.assign(helpConfig, this.help);
258
258
 
@@ -269,7 +269,7 @@ export class Field<
269
269
  class: this.labelClass,
270
270
  };
271
271
 
272
- if ((this.label as any).isComponentType) labelConfig = this.label;
272
+ if ((this.label as any).isComponentType === true) labelConfig = this.label;
273
273
  else if (isSelector(this.label)) labelConfig.text = this.label;
274
274
  else Object.assign(labelConfig, this.label);
275
275
 
@@ -43,7 +43,7 @@ export class LabeledContainer extends FieldGroup<LabeledContainerConfig> {
43
43
  required: true,
44
44
  };
45
45
 
46
- if ((this.label as any).isComponentType) labelConfig = this.label as Record<string, unknown>;
46
+ if ((this.label as any).isComponentType === true) labelConfig = this.label as Record<string, unknown>;
47
47
  else if (isSelector(this.label)) labelConfig.text = this.label;
48
48
  else Object.assign(labelConfig, this.label);
49
49
 
@@ -1280,7 +1280,14 @@ export class Grid<T = unknown> extends ContainerBase<GridConfig<T>, GridInstance
1280
1280
  );
1281
1281
  }
1282
1282
 
1283
- renderHeader(context: RenderingContext, instance: GridInstance, key: any, fixed: any, fixedColumns: any) {
1283
+ renderHeader(
1284
+ context: RenderingContext,
1285
+ instance: GridInstance,
1286
+ key: any,
1287
+ fixed: any,
1288
+ fixedColumns: any,
1289
+ repeated?: boolean,
1290
+ ) {
1284
1291
  let { data, widget, header } = instance;
1285
1292
 
1286
1293
  let { CSS, baseClass } = widget;
@@ -1458,10 +1465,12 @@ export class Grid<T = unknown> extends ContainerBase<GridConfig<T>, GridInstance
1458
1465
 
1459
1466
  if (headerRows.length == 0) return null;
1460
1467
 
1461
- return (
1462
- <tbody key={"h" + key} className={CSS.element(baseClass, "header")}>
1463
- {headerRows}
1464
- </tbody>
1468
+ //headers repeated for each group sit between the data rows, where a second thead
1469
+ //would not be allowed
1470
+ return VDOM.createElement(
1471
+ repeated ? "tbody" : "thead",
1472
+ { key: "h" + key, className: CSS.element(baseClass, "header") },
1473
+ headerRows,
1465
1474
  );
1466
1475
  }
1467
1476
 
@@ -1582,7 +1591,9 @@ export class Grid<T = unknown> extends ContainerBase<GridConfig<T>, GridInstance
1582
1591
  data-group-element={`group-caption-${level}`}
1583
1592
  >
1584
1593
  <tr>
1585
- <td colSpan={1000}>{caption}</td>
1594
+ <td colSpan={1000} role="columnheader">
1595
+ {caption}
1596
+ </td>
1586
1597
  </tr>
1587
1598
  </tbody>
1588
1599
  );
@@ -1644,7 +1655,13 @@ export class Grid<T = unknown> extends ContainerBase<GridConfig<T>, GridInstance
1644
1655
  if (pad !== false) cls += (cls ? " " : "") + CSS.state("pad");
1645
1656
 
1646
1657
  return (
1647
- <td key={i} className={cls} colSpan={colSpan} style={style}>
1658
+ <td
1659
+ key={i}
1660
+ className={cls}
1661
+ colSpan={colSpan}
1662
+ role={c.caption ? "columnheader" : undefined}
1663
+ style={style}
1664
+ >
1648
1665
  {v}
1649
1666
  </td>
1650
1667
  );
@@ -1820,9 +1837,11 @@ export class Grid<T = unknown> extends ContainerBase<GridConfig<T>, GridInstance
1820
1837
  );
1821
1838
 
1822
1839
  if (g.showHeader) {
1823
- record.vdom.push(this.renderHeader(context, instance, record.key + "-header", false, false));
1840
+ record.vdom.push(this.renderHeader(context, instance, record.key + "-header", false, false, true));
1824
1841
  if (hasFixedColumns)
1825
- record.fixedVdom.push(this.renderHeader(context, instance, record.key + "-header", false, true));
1842
+ record.fixedVdom.push(
1843
+ this.renderHeader(context, instance, record.key + "-header", false, true, true),
1844
+ );
1826
1845
  }
1827
1846
  }
1828
1847
 
@@ -2242,6 +2261,25 @@ class GridComponent extends VDOM.Component<GridComponentProps, GridComponentStat
2242
2261
  };
2243
2262
  }
2244
2263
 
2264
+ groupLooseRows(items: any[]): any[] {
2265
+ let { CSS, baseClass } = this.props.instance.widget;
2266
+ let result: any[] = [];
2267
+ for (let i = 0; i < items.length; i++) {
2268
+ if (!items[i]?.props?.useTrTag) {
2269
+ result.push(items[i]);
2270
+ continue;
2271
+ }
2272
+ let start = i;
2273
+ while (i + 1 < items.length && items[i + 1]?.props?.useTrTag) i++;
2274
+ result.push(
2275
+ <tbody key={`rows-${items[start].key}`} className={CSS.element(baseClass, "row-group")}>
2276
+ {items.slice(start, i + 1)}
2277
+ </tbody>,
2278
+ );
2279
+ }
2280
+ return result;
2281
+ }
2282
+
2245
2283
  render() {
2246
2284
  let { instance, data, fixedFooter, fixedColumnsFixedFooter } = this.props;
2247
2285
  let { widget, hasFixedColumns } = instance;
@@ -2495,6 +2533,13 @@ class GridComponent extends VDOM.Component<GridComponentProps, GridComponentStat
2495
2533
  }
2496
2534
  }
2497
2535
 
2536
+ //grids with merged cells render rows as tr elements, as rowspan cannot cross row group
2537
+ //boundaries, so consecutive rows have to share a tbody instead of getting one each
2538
+ if (instance.row.hasMergedCells) {
2539
+ children = this.groupLooseRows(children);
2540
+ if (hasFixedColumns) fixedChildren = this.groupLooseRows(fixedChildren);
2541
+ }
2542
+
2498
2543
  let shouldRenderFixedFooter = widget.scrollable && (fixedFooter || fixedColumnsFixedFooter);
2499
2544
 
2500
2545
  if (hasFixedColumns) {
@@ -3526,20 +3571,23 @@ class GridComponent extends VDOM.Component<GridComponentProps, GridComponentStat
3526
3571
  if (scrollIntoView) {
3527
3572
  let record = this.getRecordAt(index)!;
3528
3573
 
3529
- let item = record && this.dom.table!.querySelector(`tbody[data-record-key="${record.key}"]`);
3574
+ //rows are tbody elements, except in grids with merged cells, where they are tr
3575
+ let cellsOf = (rowEl: Element) =>
3576
+ (rowEl.tagName == "TR" ? rowEl : (rowEl.firstChild as HTMLElement)).children;
3577
+
3578
+ let item = record && this.dom.table!.querySelector(`[data-record-key="${record.key}"]`);
3530
3579
 
3531
3580
  let hscroll = false;
3532
3581
  if (item) {
3533
3582
  if (widget.cellEditable)
3534
3583
  if (this.state.cursorCellIndex >= this.props.instance.fixedColumnCount) {
3535
3584
  hscroll = true;
3536
- item = (item.firstChild as HTMLElement)!.children[
3585
+ item = cellsOf(item)[
3537
3586
  this.state.cursorCellIndex - this.props.instance.fixedColumnCount
3538
3587
  ] as Element;
3539
3588
  } else {
3540
- let fixedItem = this.dom.fixedTable!.querySelector(`tbody[data-record-key="${record.key}"]`);
3541
- let cell =
3542
- fixedItem && (fixedItem.firstChild as HTMLElement)!.children[this.state.cursorCellIndex];
3589
+ let fixedItem = this.dom.fixedTable!.querySelector(`[data-record-key="${record.key}"]`);
3590
+ let cell = fixedItem && cellsOf(fixedItem)[this.state.cursorCellIndex];
3543
3591
  if (cell) scrollElementIntoView(cell, false, true, 10);
3544
3592
  }
3545
3593
 
@@ -1,41 +1,41 @@
1
- export { Widget } from "../ui/Widget";
2
- export { Text } from "../ui/Text";
3
- export { StaticText } from "../ui/StaticText";
4
- export { PureContainer } from "../ui/PureContainer";
5
- export { IsolatedScope } from "../ui/IsolatedScope";
6
- export { DetachedScope } from "../ui/DetachedScope";
7
- export { Restate, PrivateStore } from "../ui/Restate";
8
- export { DataProxy } from "../ui/DataProxy";
9
- export { Content } from "../ui/layout/Content";
10
- export { ContentPlaceholder, ContentPlaceholderScope } from "../ui/layout/ContentPlaceholder";
11
- export { LabelsTopLayout, LabelsTopLayoutCell } from "../ui/layout/LabelsTopLayout";
12
- export { LabelsLeftLayout } from "../ui/layout/LabelsLeftLayout";
13
- export { ContentResolver } from "../ui/ContentResolver";
14
- export { Rescope } from "../ui/Rescope";
15
- export { Repeater } from "../ui/Repeater";
16
-
17
- export * from "./cx";
18
- export * from "./HtmlElement";
19
- export * from "./Button";
20
- export * from "./DocumentTitle";
21
- export * from "./List";
22
- export * from "./Sandbox";
23
- export * from "./CxCredit";
24
- export * from "./Heading";
25
- export * from "./Section";
26
- export * from "./FlexBox";
27
- export * from "./Icon";
28
- export * from "./ProgressBar";
29
- export * from "./Resizer";
30
- export * from "./HighlightedSearchText";
31
- export * from "./autoFocus";
32
- export * from "./ReactElementWrapper";
33
-
34
- export * from "./icons/index";
35
- export * from "./overlay/index";
36
- export * from "./nav/index";
37
- export * from "./form/index";
38
- export * from "./grid/index";
39
- export * from "./drag-drop/index";
40
-
41
- export * from "./enableAllInternalDependencies";
1
+ export { Widget } from "../ui/Widget";
2
+ export { Text } from "../ui/Text";
3
+ export { StaticText } from "../ui/StaticText";
4
+ export { PureContainer } from "../ui/PureContainer";
5
+ export { IsolatedScope } from "../ui/IsolatedScope";
6
+ export { DetachedScope } from "../ui/DetachedScope";
7
+ export { Restate, PrivateStore } from "../ui/Restate";
8
+ export { DataProxy } from "../ui/DataProxy";
9
+ export { Content } from "../ui/layout/Content";
10
+ export { ContentPlaceholder, ContentPlaceholderScope } from "../ui/layout/ContentPlaceholder";
11
+ export { LabelsTopLayout, LabelsTopLayoutCell } from "../ui/layout/LabelsTopLayout";
12
+ export { LabelsLeftLayout } from "../ui/layout/LabelsLeftLayout";
13
+ export { ContentResolver } from "../ui/ContentResolver";
14
+ export { Rescope } from "../ui/Rescope";
15
+ export { Repeater } from "../ui/Repeater";
16
+
17
+ export * from "./cx";
18
+ export * from "./HtmlElement";
19
+ export * from "./Button";
20
+ export * from "./DocumentTitle";
21
+ export * from "./List";
22
+ export * from "./Sandbox";
23
+ export * from "./CxCredit";
24
+ export * from "./Heading";
25
+ export * from "./Section";
26
+ export * from "./FlexBox";
27
+ export * from "./Icon";
28
+ export * from "./ProgressBar";
29
+ export * from "./Resizer";
30
+ export * from "./HighlightedSearchText";
31
+ export * from "./autoFocus";
32
+ export * from "./ReactElementWrapper";
33
+
34
+ export * from "./icons/index";
35
+ export * from "./overlay/index";
36
+ export * from "./nav/index";
37
+ export * from "./form/index";
38
+ export * from "./grid/index";
39
+ export * from "./drag-drop/index";
40
+
41
+ export * from "./enableAllInternalDependencies";
@@ -240,10 +240,14 @@ export class OverlayBase<
240
240
  } else if (context.options.dismiss) instance.dismiss = context.options.dismiss;
241
241
 
242
242
  if (instance.dismiss) {
243
- context.push("parentOptions", {
243
+ let parentOptions = {
244
244
  ...context.parentOptions,
245
245
  dismiss: instance.dismiss,
246
- });
246
+ };
247
+ context.push("parentOptions", parentOptions);
248
+ // The overlay's own instance captured parentOptions before this point, so
249
+ // a controller attached to the overlay would not see its own dismiss.
250
+ instance.parentOptions = parentOptions;
247
251
  }
248
252
 
249
253
  if (instance.cache("dismiss", instance.dismiss)) instance.markShouldUpdate(context);
@@ -0,0 +1,59 @@
1
+ import { createModel } from "../../data/createAccessorModelProxy";
2
+ import { Store } from "../../data/Store";
3
+ import { Controller } from "../../ui/Controller";
4
+ import { act, createTestRenderer } from "../../util/test/createTestRenderer";
5
+ import { Window } from "./Window";
6
+ import assert from "assert";
7
+
8
+ interface Model {
9
+ visible: boolean;
10
+ }
11
+
12
+ describe("Window", () => {
13
+ const m = createModel<Model>();
14
+
15
+ it("can be dismissed by a controller attached to the window", async () => {
16
+ let controller: WindowController | undefined;
17
+
18
+ class WindowController extends Controller {
19
+ onInit() {
20
+ controller = this;
21
+ }
22
+ }
23
+
24
+ let store = new Store({ data: { visible: true } });
25
+ await createTestRenderer(
26
+ store,
27
+ <Window visible={m.visible} inline controller={WindowController}>
28
+ Content
29
+ </Window>,
30
+ );
31
+
32
+ assert.ok(controller, "controller was not initialized");
33
+ assert.equal(typeof controller.instance.parentOptions?.dismiss, "function");
34
+ await act(async () => controller!.instance.parentOptions.dismiss());
35
+ assert.equal(store.get(m.visible), false);
36
+ });
37
+
38
+ it("can be dismissed by a controller inside the window", async () => {
39
+ let controller: ContentController | undefined;
40
+
41
+ class ContentController extends Controller {
42
+ onInit() {
43
+ controller = this;
44
+ }
45
+ }
46
+
47
+ let store = new Store({ data: { visible: true } });
48
+ await createTestRenderer(
49
+ store,
50
+ <Window visible={m.visible} inline>
51
+ <div controller={ContentController}>Content</div>
52
+ </Window>,
53
+ );
54
+
55
+ assert.ok(controller, "controller was not initialized");
56
+ await act(async () => controller!.instance.parentOptions.dismiss());
57
+ assert.equal(store.get(m.visible), false);
58
+ });
59
+ });