Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .playwright/screenshots/alignment-mixed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .playwright/screenshots/display-alignment-ol.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .playwright/screenshots/display-alignment-ul.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/enriched-text-all-list-types.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/enriched-text-checkbox-list-checked.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/enriched-text-checkbox-list-wrapped.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/images-checkbox-list-placeholder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/list-keyboard-enter-checkbox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
139 changes: 139 additions & 0 deletions .playwright/tests/enrichedTextTextAlignmentVisual.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { test, expect, type Locator, type Page } from '@playwright/test';

test.setTimeout(90_000);

const sel = {
root: '[data-testid="test-enriched-text-root"]',
htmlInput: '[data-testid="test-enriched-text-html-input"]',
setValueButton: '[data-testid="test-enriched-text-set-value-button"]',
toggleWidthButton: '[data-testid="test-enriched-text-toggle-width-button"]',
valueOutput: '[data-testid="test-enriched-text-value-output"]',
display: '[data-testid="test-enriched-text-display"]',
displayInner: '[data-testid="test-enriched-text-display"] .et-view',
} as const;

function displayLocator(page: Page): Locator {
return page.locator(sel.display);
}

async function gotoTestEnrichedText(page: Page): Promise<void> {
await page.goto('/test-enriched-text');
await page.waitForSelector(sel.displayInner);
await page.click(sel.toggleWidthButton);
}

async function setEnrichedTextValue(page: Page, html: string): Promise<void> {
await page.fill(sel.htmlInput, html);
await page.click(sel.setValueButton);

await expect
.poll(async () => (await page.locator(sel.valueOutput).textContent()) ?? '')
.toBe(html);
}

test.describe('visual: alignments for typography and blocks', () => {
const cases = [
{
name: 'paragraphs with mixed alignments',
snapshot: 'display-alignment-paragraphs.png',
html: [
'<html>',
'<p style="text-align: left">Left aligned (default)</p>',
'<p style="text-align: center">Center aligned</p>',
'<p style="text-align: right">Right aligned</p>',
'<p style="text-align: justify">Justified alignment. This needs a bit more text to properly visualize the justification spreading across the entire width of the container, ensuring both the left and right edges are perfectly flush against the margins.</p>',
'</html>',
].join(''),
},
{
name: 'headings with mixed alignments',
snapshot: 'display-alignment-headings.png',
html: [
'<html>',
'<h2 style="text-align: center">H2 Center</h2>',
'<h4 style="text-align: right">H4 Right</h4>',
'<h6 style="text-align: left">H6 Left</h6>',
'</html>',
].join(''),
},
{
name: 'blockquotes with independent alignments',
snapshot: 'display-alignment-blockquotes.png',
html: [
'<html>',
'<blockquote>',
'<p style="text-align: left">Quote left</p>',
'<p style="text-align: center">Quote center</p>',
'<p style="text-align: right">Quote right</p>',
'</blockquote>',
'</html>',
].join(''),
},
];

for (const c of cases) {
test(c.name, async ({ page }) => {
await gotoTestEnrichedText(page);
await setEnrichedTextValue(page, c.html);
await expect(displayLocator(page)).toHaveScreenshot(c.snapshot);
});
}
});

test.describe('visual: alignments for all list types', () => {
const cases = [
{
name: 'unordered lists: left, center, right',
snapshot: 'display-alignment-ul.png',
html: [
'<html>',
'<ul style="text-align: left"><li>Left unordered 1</li><li>Left unordered 2</li></ul>',
'<ul style="text-align: center"><li>Center unordered 1</li><li>Center unordered 2</li></ul>',
'<ul style="text-align: right"><li>Right unordered 1</li><li>Right unordered 2</li></ul>',
'</html>',
].join(''),
},
{
name: 'ordered lists: left, center, right',
snapshot: 'display-alignment-ol.png',
html: [
'<html>',
'<ol style="text-align: left"><li>Left ordered 1</li><li>Left ordered 2</li></ol>',
'<ol style="text-align: center"><li>Center ordered 1</li><li>Center ordered 2</li></ol>',
'<ol style="text-align: right"><li>Right ordered 1</li><li>Right ordered 2</li></ol>',
'</html>',
].join(''),
},
{
name: 'checkbox lists: left, center, right',
snapshot: 'display-alignment-checkbox.png',
html: [
'<html>',
'<ul data-type="checkbox" style="text-align: left"><li checked>Left task 1</li><li>Left task 2</li></ul>',
'<ul data-type="checkbox" style="text-align: center"><li checked>Center task 1</li><li>Center task 2</li></ul>',
'<ul data-type="checkbox" style="text-align: right"><li checked>Right task 1</li><li>Right task 2</li></ul>',
'</html>',
].join(''),
},
{
name: 'complex mixed lists and layouts',
snapshot: 'display-alignment-mixed-lists.png',
html: [
'<html>',
'<p style="text-align: center">A centered paragraph before lists</p>',
'<ol style="text-align: right"><li>Right ordered</li></ol>',
'<ul style="text-align: center"><li>Center bullet</li></ul>',
'<ul data-type="checkbox" style="text-align: right"><li checked>Right checkbox</li></ul>',
'</html>',
].join(''),
},
];

for (const c of cases) {
test(c.name, async ({ page }) => {
await gotoTestEnrichedText(page);
await setEnrichedTextValue(page, c.html);
await expect(displayLocator(page)).toHaveScreenshot(c.snapshot);
});
}
});
19 changes: 18 additions & 1 deletion apps/example-web/src/testScreens/TestEnrichedText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const INITIAL_VALUE = '<html><p></p></html>';
export function TestEnrichedText() {
const [htmlInput, setHtmlInput] = useState(INITIAL_VALUE);
const [value, setValue] = useState(INITIAL_VALUE);
const [isWide, setIsWide] = useState(false);

return (
<div data-testid="test-enriched-text-root">
Expand All @@ -16,13 +17,23 @@ export function TestEnrichedText() {
style={enrichedTextContainerStyle}
>
<EnrichedText
style={enrichedTextStyle}
style={isWide ? enrichedTextWideStyle : enrichedTextStyle}
htmlStyle={WEB_DEFAULT_HTML_STYLE}
>
{value}
</EnrichedText>
</div>

<button
type="button"
data-testid="test-enriched-text-toggle-width-button"
onClick={() => {
setIsWide((prev) => !prev);
}}
>
Toggle width
</button>

<textarea
data-testid="test-enriched-text-html-input"
value={htmlInput}
Expand Down Expand Up @@ -55,6 +66,12 @@ const enrichedTextStyle: TextStyle = {
fontSize: 16,
};

const enrichedTextWideStyle: TextStyle = {
...enrichedTextStyle,
minWidth: 360,
maxWidth: 720,
};

const enrichedTextContainerStyle = {
width: 'fit-content',
};
84 changes: 56 additions & 28 deletions src/web/EnrichedText.css
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,29 @@
text-decoration-line: var(--et-link-text-decoration-line);
}

.eti-editor li,
.et-view li {
margin: 0;
min-height: 1.2em;
}
Comment thread
hejsztynx marked this conversation as resolved.

@supports (min-height: 1lh) {
.eti-editor li,
.et-view li {
min-height: 1lh;
}
}

/* Zero-width space to correctly display empty list elements */
.eti-editor ul:not([data-type]) > li > p:empty::after,
.et-view ul:not([data-type]) > li > p:empty::after,
.eti-editor ol > li > p:empty::after,
.et-view ol > li > p:empty::after,
.eti-editor ul[data-type='checkboxList'] > li > div:empty::after,
.et-view ul[data-type="checkbox"] > li > label:empty::after {
content: '\200b';
}

.eti-editor ul:not([data-type]),
.et-view ul:not([data-type]) {
margin: 0;
Expand All @@ -128,12 +151,14 @@
.eti-editor ul:not([data-type]) > li,
.et-view ul:not([data-type]) > li {
margin: 0;
display: flex;
align-items: baseline;
}
Comment thread
hejsztynx marked this conversation as resolved.

.eti-editor ul:not([data-type]) > li::before,
.et-view ul:not([data-type]) > li::before {
content: '';
display: inline-block;
flex-shrink: 0;
width: var(--et-ul-bullet-size, 8px);
height: var(--et-ul-bullet-size, 8px);
border-radius: 50%;
Expand All @@ -142,7 +167,6 @@
margin-left: calc(-1 * (var(--et-ul-bullet-size, 8px) + var(--et-ul-gap-width, 16px)));
margin-right: var(--et-ul-gap-width, 16px);

vertical-align: middle;
position: relative;
top: -0.1em;
}
Expand All @@ -159,12 +183,14 @@
.et-view ol > li {
margin: 0;
counter-increment: et-ol;
display: flex;
align-items: baseline;
}
Comment thread
hejsztynx marked this conversation as resolved.

.eti-editor ol > li::before,
.et-view ol > li::before {
content: counter(et-ol) '.';
display: inline-block;
flex-shrink: 0;
width: var(--et-ol-margin-left, 16px);
text-align: right;
margin-right: calc(var(--et-ol-gap-width, 16px) / 2);
Expand All @@ -173,17 +199,15 @@
color: var(--et-ol-marker-color, currentColor);
font-weight: var(--et-ol-marker-font-weight, inherit);
white-space: nowrap;
vertical-align: baseline;
}

.eti-editor li > p,
.et-view li > p {
display: inline;
margin: 0;
}

.eti-editor li,
.et-view li {
.eti-editor ul:not([data-type]) > li > p,
.et-view ul:not([data-type]) > li > p,
.eti-editor ol > li > p,
.et-view ol > li > p {
display: block;
flex: 1 1 auto;
min-width: 0;
margin: 0;
}

Expand Down Expand Up @@ -228,20 +252,23 @@

.eti-editor ul[data-type='checkboxList'] > li {
margin: 0;
display: flex;
align-items: baseline;
}

.eti-editor ul[data-type='checkboxList'] > li > label {
display: inline-flex;
align-items: center;
flex-shrink: 0;
width: var(--et-checkbox-box-size, 24px);
height: var(--et-checkbox-box-size, 24px);

margin-left: calc(-1 * (var(--et-checkbox-box-size, 24px) + var(--et-checkbox-gap-width, 16px)));
margin-right: var(--et-checkbox-gap-width, 16px);

vertical-align: middle;
margin-top: 0;
position: relative;
top: -0.1em;
top: 0.15em;
}

.eti-editor ul[data-type='checkboxList'] > li > label input[type='checkbox'] {
Expand All @@ -252,9 +279,10 @@
accent-color: var(--et-checkbox-box-color, #0000ff);
}

.eti-editor ul[data-type='checkboxList'] > li > div,
.eti-editor ul[data-type='checkboxList'] > li > div > p {
display: inline;
.eti-editor ul[data-type='checkboxList'] > li > div {
display: block;
flex: 1 1 auto;
min-width: 0;
margin: 0;
}

Expand All @@ -267,35 +295,35 @@
}

.et-view ul[data-type="checkbox"] > li {
margin: 0
margin: 0;
display: flex;
align-items: baseline;
}

.et-view ul[data-type="checkbox"] > li > input[type='checkbox'] {
display: inline-block;
align-items: center;
flex-shrink: 0;

margin-left: calc(-1 * (var(--et-checkbox-box-size, 24px) + var(--et-checkbox-gap-width, 16px)));
margin-right: var(--et-checkbox-gap-width, 16px);

margin-top: 0;
margin-bottom: 0;

vertical-align: middle;
position: relative;
top: -0.1em;

width: var(--et-checkbox-box-size, 24px);
height: var(--et-checkbox-box-size, 24px);
flex-shrink: 0;
accent-color: var(--et-checkbox-box-color, #0000ff);

/* disable checkbox press style as it's read-only */
pointer-events: none;
-webkit-tap-highlight-color: transparent;

margin-top: 0;
position: relative;
top: 0.15em;
}

.et-view ul[data-type="checkbox"] > li > label {
display: inline;
display: block;
flex: 1 1 auto;
min-width: 0;
margin: 0;
padding: 0;
}
Expand Down
Loading
Loading