diff --git a/src/verifier-credential.ts b/src/verifier-credential.ts index ba0d838..0bad935 100644 --- a/src/verifier-credential.ts +++ b/src/verifier-credential.ts @@ -179,15 +179,23 @@ const styles = ` color: var(--vp-ink-2, #47536a); overflow: auto; max-height: 460px; white-space: pre; } .check { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 4px 16px; padding: 7px 0; font-size: .9rem; border-bottom: 1px solid var(--vp-rule, #d9dee7); } - @container (max-width: 380px) { .check { grid-template-columns: 1fr; } } .check:last-child { border-bottom: 0; } .check .k { color: var(--vp-ink-2, #47536a); } /* The mark sits inline, at the start of the value's first line. As a flex item beside the text, it stayed at the left edge once the text wrapped, - a column away from the right-aligned words it belongs to. A word too long - for the row, such as an issuer's did:key, breaks rather than running off - the card; the mark then sits just above it. */ - .check .v { text-align: right; font-weight: 500; overflow-wrap: anywhere; } + a column away from the words it belongs to. A word too long for the row, + such as an issuer's did:key, breaks rather than running off the card; the + mark then sits just above it. Start and end, not left and right, so a + right-to-left page mirrors the layout rather than breaking it. */ + .check .v { text-align: end; font-weight: 500; overflow-wrap: anywhere; } + /* A narrow card, as on a phone, stacks each row: the label, then its value + on the line under it, starting where the label does. Aligned to the + end, a short value sat at the far edge, away from its label, and a long one + wrapped ragged. After .check .v, so its text-align wins. */ + @container (max-width: 380px) { + .check { grid-template-columns: 1fr; } + .check .v { text-align: start; } + } .mark { width: 15px; height: 15px; border-radius: 50%; display: inline-grid; place-items: center; font-size: 10px; font-weight: 700; color: #fff; vertical-align: 2px; margin-right: 2px; } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; diff --git a/test/browser/states.spec.ts b/test/browser/states.spec.ts index 24691dc..c181afd 100644 --- a/test/browser/states.spec.ts +++ b/test/browser/states.spec.ts @@ -268,17 +268,75 @@ test("a value too long for the row, such as an issuer's did:key, stays inside th const first = range.getClientRects()[0]!; return { fits: card.scrollWidth <= card.clientWidth, - // Too long to sit beside it, so on the line just above, at the right + // Too long to sit beside it, so on the line just above, at the left + // where the identifier starts (the rows stack at this width) above: first.top - mark.bottom, - fromRight: Math.abs(mark.right - first.right), + fromLeft: Math.abs(mark.left - first.left), }; }); expect(layout.fits, 'the identifier runs off the card').toBe(true); expect(layout.above, 'the mark is not just above the identifier').toBeGreaterThanOrEqual(-2); expect(layout.above, 'the mark is not just above the identifier').toBeLessThan(10); - expect(layout.fromRight, 'the mark is not at the right, over the identifier').toBeLessThan(10); + expect(layout.fromLeft, 'the mark is not at the left, over the identifier').toBeLessThan(10); }); +/** + * Where each value starts, against its label and its row. In a narrow card + * (380px or less) each row stacks, label over value; a wide card keeps them + * side by side. + */ +const valueEdges = (page: Page) => + page.evaluate(() => + [...document.getElementById('vc')!.shadowRoot!.querySelectorAll('.check')].map((row) => { + const label = row.querySelector('.k')!.getBoundingClientRect(); + const mark = row.querySelector('.mark')!.getBoundingClientRect(); + const range = document.createRange(); + range.selectNodeContents(row.querySelector('.mark + span')!.lastChild!); + const lines = [...range.getClientRects()]; + return { + label: row.querySelector('.k')!.textContent, + below: mark.top >= label.bottom - 1, + fromLabelLeft: Math.abs(mark.left - label.left), + fromLabelRight: Math.abs(mark.right - label.right), + fromRowRight: Math.abs(row.getBoundingClientRect().right - lines[lines.length - 1]!.right), + }; + }), + ); + +test('stacked in a right-to-left page, each value starts under its label, at the right', async ({ page }) => { + // The alignment is start and end, not left and right, so it follows the + // page's direction. The mark's 2px margin is on its right whichever way. + await page.setViewportSize({ width: 360, height: 900 }); + await page.evaluate(() => document.documentElement.setAttribute('dir', 'rtl')); + await pick(page, 'Verified'); + for (const r of await valueEdges(page)) { + expect(r.below, `${r.label}: the value is not under its label`).toBe(true); + expect(r.fromLabelRight, `${r.label}: the value does not start under its label`).toBeLessThan(3); + } +}); + +for (const situation of ['Verified', 'Registry offline']) { + test(`stacked on a phone, each value starts under its label (${situation})`, async ({ page }) => { + // Right-aligned under a label at the left, a short value sat at the far + // edge of the card and a long one wrapped ragged, away from its label + await page.setViewportSize({ width: 360, height: 900 }); + await pick(page, situation); + for (const r of await valueEdges(page)) { + expect(r.below, `${r.label}: the value is not under its label`).toBe(true); + expect(r.fromLabelLeft, `${r.label}: the value does not start under its label`).toBeLessThan(1); + } + }); + + test(`side by side in a wide card, each value keeps to the right (${situation})`, async ({ page }) => { + await page.setViewportSize({ width: 1024, height: 900 }); + await pick(page, situation); + for (const r of await valueEdges(page)) { + expect(r.below, `${r.label}: the value went under its label`).toBe(false); + expect(r.fromRowRight, `${r.label}: the value does not end at the right`).toBeLessThan(1); + } + }); +} + /** * Found in review. The spoken headline was HTML-escaped on its way into a * `textContent` assignment, so a screen reader on the unconfirmed-issuer card