fix: lint issues in dom.ts

This commit is contained in:
JackUait 2025-11-07 01:57:29 +03:00
commit 4e7e384375
10 changed files with 428 additions and 58 deletions

View file

@ -0,0 +1,23 @@
---
alwaysApply: true
---
# Rule: DO NOT MODIFY configuration files unless explicitly instructed
## Description
You MUST **never modify any configuration files** (such as `vite.config.ts`, `tsconfig.json`, `.eslintrc`, `package.json`, `.env`, etc.) **unless explicitly told to do so** in the current request or accompanying instructions.
## Examples
✅ **Allowed**
- Editing TypeScript source files, tests, or component code.
- Updating imports, logic, or styles within non-config files.
- Adding configuration changes **only when explicitly requested** (e.g., “Add a new alias in `vite.config.ts`”).
❌ **Not Allowed**
- Modifying or creating any config files without explicit instruction.
- Automatically adding dependencies or changing build/test settings.
- Altering environment variables or global project settings without being told to.
## Enforcement
If you believe a configuration change might be required, **ask for confirmation first** before proceeding.

View file

@ -0,0 +1,23 @@
---
alwaysApply: true
---
# Fix Problems Policy
## Core Principle
VERY IMPORTANT: When encountering ANY problem in the code—such as TypeScript errors, linting issues, runtime bugs, accessibility violations, or performance problems—you or any other problem MUST find a proper way to fix it. Do NOT silence, suppress, or avoid the problem using workarounds like `// @ts-ignore`, `any` types, or ignoring linter warnings.
## Preferred Approaches
- **Refactor for correctness**: Resolve issues by improving the code structure, using precise types, type guards, proper error handling, and best practices.
- **Investigate root causes**: Use tools like debugging, logging, or code searches to understand why the problem occurs before fixing it.
- **Align with existing rules**: Follow related policies such as the Fix TypeScript Errors Policy (adapt for other languages), ESLint configurations, and accessibility guidelines.
- **Test the fix**: After fixing, verify with tests, linting runs (e.g., `yarn lint:fix`), or manual checks to ensure the problem is truly resolved without introducing new issues.
## When to Apply
- During any code editing, reviewing, or generation task.
- Proactively scan for and fix problems in affected files using available tools (e.g., read_lints, grep, codebase_search).
- If a problem persists after reasonable efforts, document it clearly and suggest next steps rather than suppressing it.
## Notes
- This policy promotes robust, high-quality code that is easier to maintain and less prone to future issues.
- If unsure how to fix a problem, use tools to gather more information or break it into smaller, solvable parts rather than bypassing it.

View file

@ -0,0 +1,119 @@
---
alwaysApply: true
description: Enforce accessibility best practices so all users can use the application
---
### Accessibility guidance (must follow)
- Semantics first
- Prefer semantic HTML (`button`, `a`, `nav`, `main`, `header`, `footer`, `ul/ol/li`, `table/th/td`) over generic `div`/`span`.
- Use `button` for actions and `a`/`Link` for navigation. Do not use click handlers on non-interactive elements. If unavoidable, add `role="button"`, `tabIndex={0}`, and keyboard handlers for Enter/Space.
- Keyboard support
- All interactive controls must be reachable via Tab and operable via keyboard.
- Do not remove focus outlines. If customizing, ensure visible `:focus-visible` styles with sufficient contrast.
- Preserve a logical tab order; avoid `tabIndex` > 0.
- Focus management
- On opening modals/drawers/popovers: move focus inside, trap focus, and restore focus to the trigger on close.
- Provide a skip link to main content (e.g., `href="#main"`) and landmark roles (`<main>`, `<nav>`, `<header>`, `<footer>`).
- Images and media
- Every `img` must have an appropriate `alt`. If decorative, use `alt=""` and `aria-hidden="true"`.
- Provide captions/subtitles for video/audio when applicable.
- For lazy-loaded images with skeletons, mark skeletons `aria-hidden="true"` and set container `aria-busy` while loading.
- Forms
- Inputs require visible labels bound via `<label htmlFor>` or `aria-label`/`aria-labelledby`.
- Indicate errors with `aria-invalid` and associate helper/error text via `aria-describedby`.
- Live updates and async content
- For dynamic status (loading/completion), use `aria-live="polite"` (or `assertive` if critical).
- Spinners should have `aria-label` or be hidden (`aria-hidden="true"`) with a separate live region announcing status.
- Headings and structure
- Maintain a logical heading hierarchy without skipping levels.
- Use list semantics for collections.
- Color and contrast
- Ensure WCAG 2.1 AA contrast: 4.5:1 for normal text and 3:1 for large or bold text and UI components, including focus and hover states. When placing text over images, add an overlay or background.
- Do not convey information by color alone; add icons/text.
- Motion and reduced motion
- Respect `prefers-reduced-motion: reduce`. Disable or simplify non-essential animations.
- In React animations, gate effects with `gsap.matchMedia('(prefers-reduced-motion: no-preference)')` and provide a reduced-motion path.
- Example usage exists in [AnnouncementsFeedContent.tsx](mdc:src/frontend/src/features/AnnouncementsFeed/ui/AnnouncementsFeedContent.tsx).
- Vanilla CSS example using the `prefers-reduced-motion` media query:
```css
/* Default animations */
.card {
transition: transform 300ms ease, opacity 300ms ease;
}
.card:hover {
transform: translateY(-4px);
opacity: 0.95;
}
/* Reduced motion: remove transforms and long transitions */
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition-duration: 0.01ms !important; /* effectively no transition */
scroll-behavior: auto !important;
}
.card:hover {
transform: none;
opacity: 1;
}
}
```
- Tables and data
- Use `<th scope>` for headers, provide captions when helpful. Avoid layout tables.
- Testing
- Prefer `@testing-library` queries by role/name (`getByRole`, `getByLabelText`) to reflect real accessibility.
### React implementation tips
- Announce route changes by updating `document.title` and placing page content in a `<main id="main">` region.
- When building composite widgets (tabs, accordions), follow the relevant ARIA patterns (roles, `aria-selected`, `aria-controls`) only when semantics are not achievable with native elements.
- For card components that wrap links, ensure the entire card is a single focusable link (as with `Link`) and include descriptive link text or `aria-label` if needed.
### Code patterns
```tsx
// Accessible button vs. link
<button type="button" onClick={handleAction}>Do action</button>
<Link to="/path">Go to details</Link>
// Custom interactive element (only if you cannot use <button>)
<div
role="button"
tabIndex={0}
onKeyDown={(e) => {
const isEnter = e.key === 'Enter' || e.code === 'Enter';
const isSpace = e.key === ' ' || e.key === 'Spacebar' || e.code === 'Space';
if (isEnter) {
onClick();
}
if (isSpace) {
e.preventDefault();
}
}}
onKeyUp={(e) => {
const isSpace = e.key === ' ' || e.key === 'Spacebar' || e.code === 'Space';
if (isSpace) {
onClick();
}
}}
onClick={onClick}
/>
```
### Notes
- Use ARIA to enhance semantics, not replace them. Avoid redundant roles on native elements.
- If a component is purely decorative (e.g., background clouds), set `aria-hidden="true"` and remove from the tab order.

View file

@ -0,0 +1,27 @@
---
title: Frontend ESLint Code Style
alwaysApply: true
description: Defer all code style decisions to the project's ESLint configuration; do not invent new style rules
---
### Code Style Source of Truth
- **Always defer to ESLint configuration** for any code style, formatting, or lint rules.
- **Do not create or enforce custom style rules** beyond what ESLint (and its plugins) already defines in this repo.
### Where the rules live
- Frontend config: [eslint.config.js](mdc:src/frontend/eslint.config.js)
- AppShell config: [.eslintrc.js](mdc:src/Dodo.KnowledgeBase.Web/appshell/.eslintrc.js)
### How to apply
- When unsure about style (imports order, quote style, indentation, prop ordering, hooks rules, etc.), consult the ESLint configs above and follow them as-is.
- Prefer using the repo scripts to validate/fix:
- `yarn lint`
- `yarn lint:fix`
### Notes
- If ESLint and Prettier interact, follow the ESLint-integrated Prettier setup from the configs.
- For styles-in-JS (e.g., styled-components), follow any ESLint plugin guidance present; do not invent property ordering rules.

View file

@ -0,0 +1,22 @@
---
alwaysApply: true
globs: *.ts,*.tsx
description: Enforce fixing TypeScript errors by improving code quality, not suppressing them
---
# Fix TypeScript Errors Policy
- **Core Principle**: Always resolve TypeScript errors by refactoring code to be type-safe, rather than suppressing them with `any`, `// @ts-ignore`, or similar workarounds.
- **Preferred Approaches**:
- Use precise types, type guards, discriminated unions, and proper narrowing to eliminate errors.
- Avoid the non-null assertion operator (`!`) and `any` types as per project guidelines.
- Refactor functions, components, and logic to align with TypeScript's type system.
- **When to Apply**:
- For any TypeScript files (`.ts`, `.tsx`), prioritize fixing errors during edits.
- After making changes, run `yarn lint:fix` or similar commands to ensure compliance.
- **Alignment with Existing Rules**:
- This reinforces the ESLint Fix-First Policy: Fix issues flagged by TypeScript/ESLint by improving code, not silencing linters.
- Ensure accessibility and best practices are maintained while resolving types.
- **Notes**:
- If a TypeScript error persists after reasonable refactoring, consult the ESLint configuration or seek clarification on intended behavior, but do not suppress it locally.
- Promote code that is both type-safe and adheres to React/JS best practices.

View file

@ -0,0 +1,69 @@
---
alwaysApply: true
globs: "*.ts","*.tsx","*.js","*.jsx","src/frontend/**"
description: "Frontend development principle: Keep solutions simple and avoid overengineering"
---
# Frontend Simplicity Principle
When working on frontend tasks, prioritize simple, straightforward solutions over complex implementations.
## Guidelines
### Keep it simple
- **Prefer basic approaches**: Choose standard patterns over custom abstractions unless there's a clear benefit
- **Avoid premature optimization**: Don't add complexity for performance gains that haven't been measured
- **Use existing libraries**: Leverage well-established libraries rather than building custom solutions
### Component design
- **Single responsibility**: Components should do one thing well
- **Avoid deep nesting**: Keep component trees shallow and manageable
- **Prefer composition over inheritance**: Use composition patterns for reusable behavior
### State management
- **Local state first**: Use local component state before reaching for global state management
- **Simple patterns**: Prefer useState/useReducer over complex state machines unless necessary
- **Avoid over-abstraction**: Don't create unnecessary abstractions for simple state logic
### Code organization
- **Clear naming**: Use descriptive names that explain the purpose
- **Minimal files**: Avoid splitting simple features across multiple files
- **Straightforward logic**: Write code that's easy to follow and debug
### When complexity is justified
Only add complexity when:
- It solves a measured performance problem
- It significantly improves user experience
- It enables critical functionality
- The team agrees it's necessary
## Examples
```tsx
// ✅ Simple and clear
const UserProfile = ({ user }) => {
const [isEditing, setIsEditing] = useState(false);
return (
<div>
<h2>{user.name}</h2>
{isEditing ? (
<EditForm onSave={() => setIsEditing(false)} />
) : (
<button onClick={() => setIsEditing(true)}>Edit</button>
)}
</div>
);
};
// ❌ Overcomplicated
const UserProfile = ({ user }) => {
const [state, dispatch] = useReducer(profileReducer, initialProfileState);
const editingContext = useContext(EditingContext);
const formManager = useFormManager();
// Complex logic that could be simplified...
};
```
Remember: Code is read more than it's written. Choose the solution that future developers can understand quickly.

View file

@ -0,0 +1,24 @@
---
alwaysApply: true
description: Policy for handling ESLint issues by preferring autofix with yarn lint:fix
---
# Lint Fix Policy
When encountering ANY ESLint problem:
## Core Steps
1. **ALWAYS try autofix first**: Run `yarn lint:fix` (or the equivalent command for the subproject) to automatically resolve the issue.
- For frontend: From the workspace root, run `cd packages/frontend && yarn lint:fix`
- If targeting specific files: `cd packages/frontend && yarn eslint "path/to/file.tsx" --fix`
2. **ONLY manual fix if autofix fails**: If `yarn lint:fix` does not resolve the issue, manually edit the code to comply with ESLint rules.
- Defer to the ESLint configuration as the source of truth: [eslint.config.js](mdc:eslint.config.js) for frontend.
- Do not invent custom style rules; follow ESLint and integrated Prettier setups exactly.
- After manual fixes, re-run `yarn lint` to verify resolution.
## Notes
- Prefer `yarn lint:fix` over ad-hoc formatting to ensure consistency.
- If ESLint interacts with Prettier, let ESLint enforce the rules.
- For uncertainty, consult ESLint configs before manual changes.
- Proactively use this during code edits, reviews, or generations to maintain high-quality code.

View file

@ -0,0 +1,37 @@
---
alwaysApply: true
globs: tests/**/*.spec.ts,tests/**/*.ts
description: Playwright end-to-end testing patterns and expectations
---
# Playwright E2E Tests
- **Use shared fixtures**
- Import `test`/`expect` from `[fixtures.ts](mdc:tests/Dodo.KnowledgeBase.Ui/fixtures/fixtures.ts)` so page objects, API helpers, and auth utilities stay consistent.
- Prefer the `storedCookies` fixture when a test needs an authenticated context to avoid duplicate login work.
- **Lean on helpers and page objects**
- Reuse the page-object classes in `[pages/](mdc:tests/Dodo.KnowledgeBase.Ui/pages)` for interactions; add new methods there instead of ad-hoc selectors in specs.
- Wrap navigation, assertions, and Allure metadata with `test.step` via `[utils/helpers.ts](mdc:tests/Dodo.KnowledgeBase.Ui/utils/helpers.ts)` for richer reporting.
- **Prefer resilient, accessible locators**
- Target elements by role, label, or text when possible (e.g., `page.getByRole('button', { name: '...' })`) before falling back to CSS/XPath.
- Mirror the apps accessibility requirements—favor semantic selectors over brittle DOM structure hooks.
- **Keep tests focused and deterministic**
- Scope each spec to a single feature/flow; move common setup into `test.beforeEach` blocks using helpers.
- **Leverage configuration**
- Align new suites with existing Playwright projects defined in `[playwright.config.ts](mdc:playwright.config.ts)`; extend `testMatch` rather than spinning up new configs.
- Respect shared `use` options (locale, screenshots, traces) to keep reports uniform.
- **AVOID using mocks unless it's necessary**
- When writing tests prefer actual data instead of using mocks to test actual behavior.
- **Do not @allure.id to tests**
- Adding @allure.id is handled on the user's side DO NOT add it yourself.
- **Document Allure hierarchy**
- Call `Helpers.addAllureHierarchy` at suite setup (see `[auth-tests.spec.ts](mdc:tests/Dodo.KnowledgeBase.Ui/auth-tests.spec.ts)`) so new tests appear correctly in TestOps.
- **Running locally**
- Follow the workflow in `[README.md](mdc:README.md#L51)` (`yarn --cwd src/frontend serve` + `yarn e2e:ui`) when validating new specs.

View file

@ -37,7 +37,7 @@
},
"rules": {
"jsdoc/require-returns-type": "off",
"@typescript-eslint/strict-boolean-expressions": "warn",
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/consistent-type-exports": "error"
},
@ -51,7 +51,7 @@
],
"rules": {
"quotes": [1, "double"],
"semi": [1, "never"],
"semi": [1, "never"]
}
}
]

View file

@ -13,7 +13,7 @@ export default class Dom {
* @returns {boolean}
*/
public static isSingleTag(tag: HTMLElement): boolean {
return tag.tagName && [
return Boolean(tag.tagName) && [
'AREA',
'BASE',
'BR',
@ -40,10 +40,7 @@ export default class Dom {
* @returns {boolean}
*/
public static isLineBreakTag(element: HTMLElement): element is HTMLBRElement {
return element && element.tagName && [
'BR',
'WBR',
].includes(element.tagName);
return !!element && ['BR', 'WBR'].includes(element.tagName);
}
/**
@ -54,7 +51,7 @@ export default class Dom {
* @param {object} [attributes] - any attributes
* @returns {HTMLElement}
*/
public static make(tagName: string, classNames: string | (string | undefined)[] | null = null, attributes: object = {}): HTMLElement {
public static make(tagName: string, classNames: string | (string | undefined)[] | null = null, attributes: Record<string, string | number | boolean | null | undefined> = {}): HTMLElement {
const el = document.createElement(tagName);
if (Array.isArray(classNames)) {
@ -67,7 +64,17 @@ export default class Dom {
for (const attrName in attributes) {
if (Object.prototype.hasOwnProperty.call(attributes, attrName)) {
el[attrName] = attributes[attrName];
const value = attributes[attrName];
if (value === undefined || value === null) {
continue;
}
if (attrName in el) {
(el as unknown as Record<string, unknown>)[attrName] = value;
} else {
el.setAttribute(attrName, String(value));
}
}
}
@ -128,16 +135,16 @@ export default class Dom {
const temp = document.createElement('div'),
parent = el1.parentNode;
parent.insertBefore(temp, el1);
parent?.insertBefore(temp, el1);
// move el1 to right before el2
parent.insertBefore(el1, el2);
parent?.insertBefore(el1, el2);
// move el2 to right before where el1 used to be
parent.insertBefore(el2, temp);
parent?.insertBefore(el2, temp);
// remove temporary marker node
parent.removeChild(temp);
parent?.removeChild(temp);
}
/**
@ -216,50 +223,61 @@ export default class Dom {
* @returns - it can be text Node or Element Node, so that caret will able to work with it
* Can return null if node is Document or DocumentFragment, or node is not attached to the DOM
*/
public static getDeepestNode(node: Node, atLast = false): Node | null {
public static getDeepestNode(node: Node | null, atLast = false): Node | null {
/**
* Current function have two directions:
* - starts from first child and every time gets first or nextSibling in special cases
* - starts from last child and gets last or previousSibling
* - starts from first child and every time gets first or nextSibling in special cases
* - starts from last child and gets last or previousSibling
*
* @type {string}
*/
const child = atLast ? 'lastChild' : 'firstChild',
sibling = atLast ? 'previousSibling' : 'nextSibling';
if (node && node.nodeType === Node.ELEMENT_NODE && node[child]) {
let nodeChild = node[child] as Node;
/**
* special case when child is single tag that can't contain any content
*/
if (
Dom.isSingleTag(nodeChild as HTMLElement) &&
!Dom.isNativeInput(nodeChild) &&
!Dom.isLineBreakTag(nodeChild as HTMLElement)
) {
/**
* 1) We need to check the next sibling. If it is Node Element then continue searching for deepest
* from sibling
*
* 2) If single tag's next sibling is null, then go back to parent and check his sibling
* In case of Node Element continue searching
*
* 3) If none of conditions above happened return parent Node Element
*/
if (nodeChild[sibling]) {
nodeChild = nodeChild[sibling];
} else if (nodeChild.parentNode[sibling]) {
nodeChild = nodeChild.parentNode[sibling];
} else {
return nodeChild.parentNode;
}
}
return this.getDeepestNode(nodeChild, atLast);
if (node === null || node.nodeType !== Node.ELEMENT_NODE) {
return node;
}
return node;
const nodeChildProperty = node[child];
if (nodeChildProperty === null) {
return node;
}
let nodeChild = nodeChildProperty as Node;
/**
* special case when child is single tag that can't contain any content
*/
if (
Dom.isSingleTag(nodeChild as HTMLElement) &&
!Dom.isNativeInput(nodeChild) &&
!Dom.isLineBreakTag(nodeChild as HTMLElement)
) {
/**
* 1) We need to check the next sibling. If it is Node Element then continue searching for deepest
* from sibling
*
* 2) If single tag's next sibling is null, then go back to parent and check his sibling
* In case of Node Element continue searching
*
* 3) If none of conditions above happened return parent Node Element
*/
const siblingNode = nodeChild[sibling];
if (siblingNode) {
nodeChild = siblingNode;
} else {
const parentSiblingNode = nodeChild.parentNode?.[sibling];
if (!parentSiblingNode) {
return nodeChild.parentNode;
}
nodeChild = parentSiblingNode;
}
}
return this.getDeepestNode(nodeChild, atLast);
}
/**
@ -274,7 +292,7 @@ export default class Dom {
return false;
}
return node && node.nodeType && node.nodeType === Node.ELEMENT_NODE;
return node != null && node.nodeType != null && node.nodeType === Node.ELEMENT_NODE;
}
/**
@ -289,7 +307,7 @@ export default class Dom {
return false;
}
return node && node.nodeType && node.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
return node != null && node.nodeType != null && node.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
}
/**
@ -315,7 +333,7 @@ export default class Dom {
'TEXTAREA',
];
return target && target.tagName ? nativeInputs.includes(target.tagName) : false;
return target != null && typeof target.tagName === 'string' ? nativeInputs.includes(target.tagName) : false;
}
/**
@ -357,7 +375,7 @@ export default class Dom {
* @returns {boolean} true if it is empty
*/
public static isNodeEmpty(node: Node, ignoreChars?: string): boolean {
let nodeText;
let nodeText: string | undefined;
if (this.isSingleTag(node as HTMLElement) && !this.isLineBreakTag(node as HTMLElement)) {
return false;
@ -366,14 +384,14 @@ export default class Dom {
if (this.isElement(node) && this.isNativeInput(node)) {
nodeText = (node as HTMLInputElement).value;
} else {
nodeText = node.textContent.replace('\u200B', '');
nodeText = node.textContent?.replace('\u200B', '');
}
if (ignoreChars) {
nodeText = nodeText.replace(new RegExp(ignoreChars, 'g'), '');
nodeText = nodeText?.replace(new RegExp(ignoreChars, 'g'), '');
}
return nodeText.length === 0;
return (nodeText?.length ?? 0) === 0;
}
/**
@ -403,12 +421,14 @@ export default class Dom {
const treeWalker = [ node ];
while (treeWalker.length > 0) {
node = treeWalker.shift();
const currentNode = treeWalker.shift();
if (!node) {
if (!currentNode) {
continue;
}
node = currentNode;
if (this.isLeaf(node) && !this.isNodeEmpty(node, ignoreChars)) {
return false;
}
@ -450,7 +470,7 @@ export default class Dom {
return (node as Text).length;
}
return node.textContent.length;
return node.textContent?.length ?? 0;
}
/**
@ -518,7 +538,7 @@ export default class Dom {
wrapper = data;
}
const check = (element: HTMLElement): boolean => {
const check = (element: Element): boolean => {
return !Dom.blockElements.includes(element.tagName.toLowerCase()) &&
Array.from(element.children).every(check);
};
@ -539,7 +559,7 @@ export default class Dom {
return Array.from(parent.children).reduce((result, element) => {
return [...result, ...Dom.getDeepestBlockElements(element as HTMLElement)];
}, []);
}, [] as HTMLElement[]);
}
/**
@ -550,7 +570,13 @@ export default class Dom {
*/
public static getHolder(element: string | HTMLElement): HTMLElement {
if (_.isString(element)) {
return document.getElementById(element);
const holder = document.getElementById(element);
if (holder === null) {
throw new Error(`Element with id "${element}" not found`);
}
return holder;
}
return element;
@ -572,7 +598,7 @@ export default class Dom {
* @todo handle case when editor initialized in scrollable popup
* @param el - element to compute offset
*/
public static offset(el): { top: number; left: number; right: number; bottom: number } {
public static offset(el: Element): { top: number; left: number; right: number; bottom: number } {
const rect = el.getBoundingClientRect();
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
@ -682,13 +708,13 @@ export function isCollapsedWhitespaces(textContent: string): boolean {
* The calculation formula is as follows:
*
* 1. Calculate the baseline offset:
* - Typically, the baseline is about 80% of the `fontSize` from the top of the text, as this is a common average for many fonts.
* - Typically, the baseline is about 80% of the `fontSize` from the top of the text, as this is a common average for many fonts.
*
* 2. Calculate the additional space due to `lineHeight`:
* - If the `lineHeight` is greater than the `fontSize`, the extra space is evenly distributed above and below the text. This extra space is `(lineHeight - fontSize) / 2`.
* - If the `lineHeight` is greater than the `fontSize`, the extra space is evenly distributed above and below the text. This extra space is `(lineHeight - fontSize) / 2`.
*
* 3. Calculate the total baseline Y coordinate:
* - Sum of `marginTop`, `borderTopWidth`, `paddingTop`, the extra space due to `lineHeight`, and the baseline offset.
* - Sum of `marginTop`, `borderTopWidth`, `paddingTop`, the extra space due to `lineHeight`, and the baseline offset.
*
* @param element - The element to calculate the baseline for.
* @returns {number} - The Y coordinate of the text baseline from the top of the element's margin box.