@operato/data-grist 10.29.5 → 10.29.7
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/CHANGELOG.md +18 -0
- package/coverage/lcov-report/index.html +40 -40
- package/coverage/lcov-report/webpack/bootstrap.html +1 -1
- package/coverage/lcov-report/webpack/index.html +1 -1
- package/coverage/lcov-report/webpack/runtime/define property getters.html +1 -1
- package/coverage/lcov-report/webpack/runtime/global.html +1 -1
- package/coverage/lcov-report/webpack/runtime/hasOwnProperty shorthand.html +1 -1
- package/coverage/lcov-report/webpack/runtime/index.html +1 -1
- package/coverage/lcov-report/webpack/runtime/make namespace object.html +1 -1
- package/coverage/lcov.info +30482 -30377
- package/dist/src/data-card/data-card-field.js +3 -0
- package/dist/src/data-card/data-card-field.js.map +1 -1
- package/dist/src/data-grid/data-grid-accum-field.js +4 -0
- package/dist/src/data-grid/data-grid-accum-field.js.map +1 -1
- package/dist/src/data-grid/data-grid-body-style.js +2 -10
- package/dist/src/data-grid/data-grid-body-style.js.map +1 -1
- package/dist/src/data-grid/data-grid-body.d.ts +1 -0
- package/dist/src/data-grid/data-grid-body.js +20 -9
- package/dist/src/data-grid/data-grid-body.js.map +1 -1
- package/dist/src/data-grid/data-grid-field.d.ts +1 -0
- package/dist/src/data-grid/data-grid-field.js +29 -1
- package/dist/src/data-grid/data-grid-field.js.map +1 -1
- package/dist/src/data-grid/data-grid-header.js +5 -10
- package/dist/src/data-grid/data-grid-header.js.map +1 -1
- package/dist/src/data-grid/data-grid.d.ts +2 -1
- package/dist/src/data-grid/data-grid.js +72 -20
- package/dist/src/data-grid/data-grid.js.map +1 -1
- package/dist/test/accessible-name.test.js +6 -4
- package/dist/test/accessible-name.test.js.map +1 -1
- package/dist/test/field-contains-content.test.d.ts +1 -0
- package/dist/test/field-contains-content.test.js +101 -0
- package/dist/test/field-contains-content.test.js.map +1 -0
- package/dist/test/grid-single-scroller.test.d.ts +1 -0
- package/dist/test/grid-single-scroller.test.js +107 -0
- package/dist/test/grid-single-scroller.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/test/accessible-name.test.ts +6 -4
- package/test/field-contains-content.test.ts +125 -0
- package/test/grid-single-scroller.test.ts +150 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/* Elements are compared by presence, not by value: a failing element comparison makes chai render the DOM and hang the runner. */
|
|
2
|
+
import { expect, fixture } from '@open-wc/testing';
|
|
3
|
+
import { html } from 'lit';
|
|
4
|
+
import '../src/data-grist.js';
|
|
5
|
+
const settle = (ms = 150) => new Promise(resolve => setTimeout(resolve, ms));
|
|
6
|
+
async function grid() {
|
|
7
|
+
const columns = [
|
|
8
|
+
{ type: 'gutter', gutterName: 'sequence' },
|
|
9
|
+
{ type: 'string', name: 'id', hidden: true },
|
|
10
|
+
{ type: 'string', name: 'code', header: 'code', width: 120, fixed: true }
|
|
11
|
+
];
|
|
12
|
+
for (let i = 0; i < 12; i++) {
|
|
13
|
+
columns.push({ type: 'string', name: 'c' + i, header: 'col ' + i, width: 110 });
|
|
14
|
+
}
|
|
15
|
+
const records = Array.from({ length: 60 }, (_, r) => {
|
|
16
|
+
const record = { id: String(r), code: 'C-' + r };
|
|
17
|
+
for (let i = 0; i < 12; i++)
|
|
18
|
+
record['c' + i] = `v${r}-${i}`;
|
|
19
|
+
return record;
|
|
20
|
+
});
|
|
21
|
+
const el = (await fixture(html `
|
|
22
|
+
<ox-grist
|
|
23
|
+
style="width: 600px; height: 320px"
|
|
24
|
+
mode="GRID"
|
|
25
|
+
.config=${{ columns, pagination: { pages: [100] } }}
|
|
26
|
+
.fetchHandler=${async () => ({ total: records.length, records })}
|
|
27
|
+
></ox-grist>
|
|
28
|
+
`));
|
|
29
|
+
await el.fetch();
|
|
30
|
+
await settle();
|
|
31
|
+
await el.updateComplete;
|
|
32
|
+
const grist = el.renderRoot.querySelector('#grist');
|
|
33
|
+
const scroller = grist.renderRoot.querySelector('div[grid]');
|
|
34
|
+
const header = grist.renderRoot.querySelector('ox-grid-header');
|
|
35
|
+
const body = grist.renderRoot.querySelector('ox-grid-body');
|
|
36
|
+
await body.updateComplete;
|
|
37
|
+
return { grist, scroller, header, body };
|
|
38
|
+
}
|
|
39
|
+
/** The largest horizontal distance between a column's header cell and its cell in the first row. */
|
|
40
|
+
function misalignment(header, body) {
|
|
41
|
+
const heads = Array.from(header.renderRoot.querySelectorAll('[role=columnheader]'));
|
|
42
|
+
const row = body.renderRoot.querySelector('div[grid-row]');
|
|
43
|
+
const cells = Array.from(row.querySelectorAll('ox-grid-field'));
|
|
44
|
+
const n = Math.min(heads.length, cells.length);
|
|
45
|
+
let worst = 0;
|
|
46
|
+
for (let i = 0; i < n; i++) {
|
|
47
|
+
worst = Math.max(worst, Math.abs(heads[i].getBoundingClientRect().left - cells[i].getBoundingClientRect().left));
|
|
48
|
+
}
|
|
49
|
+
return { n, worst };
|
|
50
|
+
}
|
|
51
|
+
describe('grid header and body share one scroller', () => {
|
|
52
|
+
/* Resizing the grid body inside a fixture trips Chrome's benign "ResizeObserver loop" error. */
|
|
53
|
+
const ignoreResizeLoop = (e) => {
|
|
54
|
+
if (String(e.message).includes('ResizeObserver loop')) {
|
|
55
|
+
e.stopImmediatePropagation();
|
|
56
|
+
e.preventDefault();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
let previousOnError = null;
|
|
60
|
+
before(() => {
|
|
61
|
+
window.addEventListener('error', ignoreResizeLoop, true);
|
|
62
|
+
previousOnError = window.onerror;
|
|
63
|
+
window.onerror = (message, ...rest) => String(message).includes('ResizeObserver loop') ? true : previousOnError === null || previousOnError === void 0 ? void 0 : previousOnError.call(window, message, ...rest);
|
|
64
|
+
});
|
|
65
|
+
after(() => {
|
|
66
|
+
window.removeEventListener('error', ignoreResizeLoop, true);
|
|
67
|
+
window.onerror = previousOnError;
|
|
68
|
+
});
|
|
69
|
+
it('neither the header nor the body scrolls on its own', async () => {
|
|
70
|
+
const { scroller, header, body } = await grid();
|
|
71
|
+
expect(scroller.scrollWidth, 'the grid must overflow, or this measures nothing').to.be.greaterThan(scroller.clientWidth);
|
|
72
|
+
expect(getComputedStyle(body).overflowX).to.equal('visible');
|
|
73
|
+
expect(getComputedStyle(header).overflowX).to.equal('visible');
|
|
74
|
+
expect(header.offsetWidth, 'header and body span the same width').to.equal(body.offsetWidth);
|
|
75
|
+
});
|
|
76
|
+
for (const where of ['start', 'middle', 'end']) {
|
|
77
|
+
it(`every column lines up with its header at the ${where}`, async () => {
|
|
78
|
+
const { scroller, header, body } = await grid();
|
|
79
|
+
const max = scroller.scrollWidth - scroller.clientWidth;
|
|
80
|
+
scroller.scrollLeft = where === 'start' ? 0 : where === 'middle' ? Math.round(max / 2) : max;
|
|
81
|
+
await settle(50);
|
|
82
|
+
const { n, worst } = misalignment(header, body);
|
|
83
|
+
expect(n, 'columns must be drawn, or this measures nothing').to.be.greaterThan(10);
|
|
84
|
+
expect(worst).to.equal(0);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
it('the header stays on top when the rows scroll', async () => {
|
|
88
|
+
const { scroller, header } = await grid();
|
|
89
|
+
scroller.scrollTop = 600;
|
|
90
|
+
await settle(50);
|
|
91
|
+
expect(scroller.scrollTop, 'the rows must scroll, or this measures nothing').to.be.greaterThan(0);
|
|
92
|
+
expect(header.getBoundingClientRect().top).to.equal(scroller.getBoundingClientRect().top);
|
|
93
|
+
});
|
|
94
|
+
it('a focused row that was under the header is brought out below it', async () => {
|
|
95
|
+
const { grist, scroller, header, body } = await grid();
|
|
96
|
+
scroller.scrollTop = 600;
|
|
97
|
+
await settle(50);
|
|
98
|
+
grist.focused = { row: 2, column: 3 };
|
|
99
|
+
await grist.updateComplete;
|
|
100
|
+
await body.updateComplete;
|
|
101
|
+
await settle(50);
|
|
102
|
+
const focused = body.renderRoot.querySelector('ox-grid-field[focused]');
|
|
103
|
+
expect(!!focused, 'a cell must take the focus').to.equal(true);
|
|
104
|
+
expect(focused.getBoundingClientRect().top).to.be.at.least(header.getBoundingClientRect().bottom - 1);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
//# sourceMappingURL=grid-single-scroller.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grid-single-scroller.test.js","sourceRoot":"","sources":["../../test/grid-single-scroller.test.ts"],"names":[],"mappings":"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","sourcesContent":["/* Elements are compared by presence, not by value: a failing element comparison makes chai render the DOM and hang the runner. */\nimport { expect, fixture } from '@open-wc/testing'\nimport { html } from 'lit'\n\nimport '../src/data-grist.js'\n\n/**\n * Header and body move as one.\n *\n * The body used to be its own scroller, and the header a second scroller that copied the body's\n * scrollLeft from its scroll event (and the other way round). Three things went wrong:\n *\n * ```\n * gap the compositor moves the body at once; the header only moves after the main thread\n * handles the event, so on a busy screen it trails by several frames\n * shake the sync ran both ways, so a late header event put the body back one frame\n * the end when the two scroll widths differ, the header stops while the body goes on\n * ```\n *\n * Now the role=grid box is the only scroller. The header sticks to its top and the body does not\n * scroll. Nothing copies a position, so nothing can trail or pull back.\n */\n\ntype Root = HTMLElement & { renderRoot: ShadowRoot; updateComplete: Promise<unknown> }\n\nconst settle = (ms = 150) => new Promise(resolve => setTimeout(resolve, ms))\n\nasync function grid() {\n const columns: any[] = [\n { type: 'gutter', gutterName: 'sequence' },\n { type: 'string', name: 'id', hidden: true },\n { type: 'string', name: 'code', header: 'code', width: 120, fixed: true }\n ]\n for (let i = 0; i < 12; i++) {\n columns.push({ type: 'string', name: 'c' + i, header: 'col ' + i, width: 110 })\n }\n\n const records = Array.from({ length: 60 }, (_, r) => {\n const record: any = { id: String(r), code: 'C-' + r }\n for (let i = 0; i < 12; i++) record['c' + i] = `v${r}-${i}`\n return record\n })\n\n const el = (await fixture(html`\n <ox-grist\n style=\"width: 600px; height: 320px\"\n mode=\"GRID\"\n .config=${{ columns, pagination: { pages: [100] } }}\n .fetchHandler=${async () => ({ total: records.length, records })}\n ></ox-grist>\n `)) as Root & { fetch(): Promise<void> }\n\n await el.fetch()\n await settle()\n await el.updateComplete\n\n const grist = el.renderRoot.querySelector('#grist') as Root & { focused?: { row: number; column: number } }\n const scroller = grist.renderRoot.querySelector('div[grid]') as HTMLElement\n const header = grist.renderRoot.querySelector('ox-grid-header') as Root\n const body = grist.renderRoot.querySelector('ox-grid-body') as Root\n await body.updateComplete\n\n return { grist, scroller, header, body }\n}\n\n/** The largest horizontal distance between a column's header cell and its cell in the first row. */\nfunction misalignment(header: Root, body: Root) {\n const heads = Array.from(header.renderRoot.querySelectorAll('[role=columnheader]')) as HTMLElement[]\n const row = body.renderRoot.querySelector('div[grid-row]') as HTMLElement\n const cells = Array.from(row.querySelectorAll('ox-grid-field')) as HTMLElement[]\n const n = Math.min(heads.length, cells.length)\n\n let worst = 0\n for (let i = 0; i < n; i++) {\n worst = Math.max(worst, Math.abs(heads[i].getBoundingClientRect().left - cells[i].getBoundingClientRect().left))\n }\n return { n, worst }\n}\n\ndescribe('grid header and body share one scroller', () => {\n /* Resizing the grid body inside a fixture trips Chrome's benign \"ResizeObserver loop\" error. */\n const ignoreResizeLoop = (e: ErrorEvent) => {\n if (String(e.message).includes('ResizeObserver loop')) {\n e.stopImmediatePropagation()\n e.preventDefault()\n }\n }\n let previousOnError: OnErrorEventHandler = null\n before(() => {\n window.addEventListener('error', ignoreResizeLoop, true)\n previousOnError = window.onerror\n window.onerror = (message, ...rest) =>\n String(message).includes('ResizeObserver loop') ? true : previousOnError?.call(window, message, ...rest)\n })\n after(() => {\n window.removeEventListener('error', ignoreResizeLoop, true)\n window.onerror = previousOnError\n })\n\n it('neither the header nor the body scrolls on its own', async () => {\n const { scroller, header, body } = await grid()\n\n expect(scroller.scrollWidth, 'the grid must overflow, or this measures nothing').to.be.greaterThan(\n scroller.clientWidth\n )\n expect(getComputedStyle(body).overflowX).to.equal('visible')\n expect(getComputedStyle(header).overflowX).to.equal('visible')\n expect(header.offsetWidth, 'header and body span the same width').to.equal(body.offsetWidth)\n })\n\n for (const where of ['start', 'middle', 'end'] as const) {\n it(`every column lines up with its header at the ${where}`, async () => {\n const { scroller, header, body } = await grid()\n\n const max = scroller.scrollWidth - scroller.clientWidth\n scroller.scrollLeft = where === 'start' ? 0 : where === 'middle' ? Math.round(max / 2) : max\n await settle(50)\n\n const { n, worst } = misalignment(header, body)\n expect(n, 'columns must be drawn, or this measures nothing').to.be.greaterThan(10)\n expect(worst).to.equal(0)\n })\n }\n\n it('the header stays on top when the rows scroll', async () => {\n const { scroller, header } = await grid()\n\n scroller.scrollTop = 600\n await settle(50)\n\n expect(scroller.scrollTop, 'the rows must scroll, or this measures nothing').to.be.greaterThan(0)\n expect(header.getBoundingClientRect().top).to.equal(scroller.getBoundingClientRect().top)\n })\n\n it('a focused row that was under the header is brought out below it', async () => {\n const { grist, scroller, header, body } = await grid()\n\n scroller.scrollTop = 600\n await settle(50)\n\n grist.focused = { row: 2, column: 3 }\n await grist.updateComplete\n await body.updateComplete\n await settle(50)\n\n const focused = body.renderRoot.querySelector('ox-grid-field[focused]') as HTMLElement\n expect(!!focused, 'a cell must take the focus').to.equal(true)\n expect(focused.getBoundingClientRect().top).to.be.at.least(header.getBoundingClientRect().bottom - 1)\n })\n})\n"]}
|