diff --git a/docs/articles/basics.md b/docs/articles/basics.md index cb157a4..a273b7e 100644 --- a/docs/articles/basics.md +++ b/docs/articles/basics.md @@ -63,7 +63,7 @@ const TextPage = lazy(() => import('./pages/Text')); // Create the renderer and load fonts const { render } = createRenderer(); -loadFonts(fonts); +await loadFonts(fonts); // Render the app using the HashRouter from SolidRouter render(() => ( diff --git a/docs/articles/migration-2x-to-3.0.md b/docs/articles/migration-2x-to-3.0.md index d1947a8..3ec05b5 100644 --- a/docs/articles/migration-2x-to-3.0.md +++ b/docs/articles/migration-2x-to-3.0.md @@ -122,7 +122,7 @@ Shaders are imported when needed by application. The following example shows how ```typescript const { renderer, render } = createRenderer(); -loadFonts(fonts); +await loadFonts(fonts); // Prepare for RC3 of Renderer import { Rounded, diff --git a/docs/essentials/render.md b/docs/essentials/render.md index 9234b90..9c89b8d 100644 --- a/docs/essentials/render.md +++ b/docs/essentials/render.md @@ -54,9 +54,11 @@ For the latest renderer options read the official [renderer documentation](https ### Config.rendererOptions - **appWidth**: Authored logical pixel width of the application. + - _Default_: `1920` - **appHeight**: Authored logical pixel height of the application. + - _Default_: `1080` - **txMemByteThreshold**: Texture Memory Byte Threshold. When the GPU VRAM used by textures exceeds this threshold, non-visible textures are freed. Set to `0` to disable. @@ -64,24 +66,30 @@ For the latest renderer options read the official [renderer documentation](https - **boundsMargin**: Bounds margin to extend the boundary for adding a CoreNode as Quad. Can be a single number or an array of four numbers. - **deviceLogicalPixelRatio**: Factor to convert app-authored logical coordinates to device logical coordinates. Supports auto-scaling for different resolutions. + - _Default_: `1` - **devicePhysicalPixelRatio**: Factor to convert device logical coordinates to device physical coordinates. Controls the number of physical pixels used per logical pixel. + - _Default_: `window.devicePixelRatio` - **clearColor**: RGBA encoded number for the background color. + - _Default_: `0x00000000` - **Texture Memory Manager Settings**: textureMemory?: Partial; - **fpsUpdateInterval**: Interval in milliseconds for receiving FPS updates. Set to `0` to disable. + - _Default_: `0` - **enableContextSpy**: Includes WebGL context call information in FPS updates. Significantly impacts performance. + - _Default_: `false` - **numImageWorkers**: Number of image workers to use. Improves image loading on multi-core devices. Set to `0` to disable. + - _Default_: `2` - **inspector** diff --git a/docs/essentials/text.md b/docs/essentials/text.md index e38a31a..9439ba1 100644 --- a/docs/essentials/text.md +++ b/docs/essentials/text.md @@ -74,7 +74,7 @@ Then you'll need to register the custom font in the AppCoreExtensions file: } as const]; // must be called after createRenderer but before render -loadFonts(fonts); +await loadFonts(fonts); ``` From this moment on you'll be able to use the font `ComicSans` anywhere in your App: diff --git a/docs/primitives/createTag.md b/docs/primitives/createTag.md index 8d35136..6078465 100644 --- a/docs/primitives/createTag.md +++ b/docs/primitives/createTag.md @@ -58,6 +58,7 @@ const App = () => { Creates a tag component from the provided children. - **Parameters**: + - `children`: The SolidTV/SolidTV elements to render into the texture. - **Returns**: diff --git a/docs/primitives/grid.md b/docs/primitives/grid.md index 02da663..5eedc44 100644 --- a/docs/primitives/grid.md +++ b/docs/primitives/grid.md @@ -23,15 +23,18 @@ The `Grid` component is simplified version of combining Column & Row and doesn't ### Behavior 1. **Grid Navigation**: + - The `Grid` supports navigation via `onUp`, `onDown`, `onLeft`, and `onRight` events. - Navigation is column-aware, ensuring horizontal navigation stays within the same row unless looping is enabled. 2. **Looping**: + - When `looping` is enabled, navigation wraps around when reaching the grid boundaries. - For vertical navigation, reaching the last row loops to the first row and vice versa. - For horizontal navigation, reaching the end of a row wraps to the start of the same row. 3. **Focus Handling**: + - The `Grid` manages focus internally using a `focusedIndex` signal. - The `onSelectedChanged` callback is invoked when the focused index changes. diff --git a/docs/primitives/row_column.md b/docs/primitives/row_column.md index 320a582..87447fd 100644 --- a/docs/primitives/row_column.md +++ b/docs/primitives/row_column.md @@ -34,15 +34,18 @@ The components accept the following props: #### Behavior 1. **Navigation**: + - The `Row` listens for left and right navigation events. - The `Column` listens for up and down navigation events. - The `onLeft` and `onRight` handlers can be customized, and default navigation logic is provided via `handleNavigation`. 2. **Focus Management**: + - Focus events are handled via the `onFocus` prop. - When focus changes, the row ensures that the currently selected child is highlighted and optionally scrolls into view. 3. **Scrolling**: + - The `withScrolling` utility is used to handle scrolling behavior when the `scroll` prop is set to `'auto'` or `'always'`. 4. **Styling**: diff --git a/eslint.config.js b/eslint.config.js index 53c4bb4..12acb7b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -40,6 +40,7 @@ export default tseslint.config( parserOptions: { project: true, tsconfigRootDir: import.meta.dirname, + warnOnUnsupportedTypeScriptVersion: false, }, globals: { ...globals.browser, diff --git a/src/core/dom-renderer/domRenderer.ts b/src/core/dom-renderer/domRenderer.ts index 5fb1be0..419d62e 100644 --- a/src/core/dom-renderer/domRenderer.ts +++ b/src/core/dom-renderer/domRenderer.ts @@ -233,48 +233,88 @@ function updateNodeParent(node: DOMNode | DOMText) { } } -function updateNodeStyles(node: DOMNode | DOMText) { - const { props } = node; +/** + * Builds the CSS transform string from node props (x, y, rotation, scale, mount). + * Returns an empty string if no transform is needed. + */ +function buildTransformCSS(props: IRendererNodeProps): string { + const transforms: string[] = []; - let style = `position: absolute; z-index: ${props.zIndex}; opacity: ${props.alpha ?? 1};`; + const { x, y } = props; + const hasMountX = props.mountX != null && props.mountX !== 0; + const hasMountY = props.mountY != null && props.mountY !== 0; - if (props.clipping) { - style += `overflow: hidden;`; - } + if (x !== 0) transforms.push(`translateX(${x}px)`); + if (hasMountX) transforms.push(`translateX(${-props.mountX * 100}%)`); - // Transform - { - let transform = ''; + if (y !== 0) transforms.push(`translateY(${y}px)`); + if (hasMountY) transforms.push(`translateY(${-props.mountY * 100}%)`); - const { x, y } = props; + if (props.rotation !== 0) transforms.push(`rotate(${props.rotation}rad)`); - const hasMountX = props.mountX != null && props.mountX !== 0; - const hasMountY = props.mountY != null && props.mountY !== 0; + if (props.scale !== 1 && props.scale != null) { + transforms.push(`scale(${props.scale})`); + } else { + if (props.scaleX !== 1) transforms.push(`scaleX(${props.scaleX})`); + if (props.scaleY !== 1) transforms.push(`scaleY(${props.scaleY})`); + } - if (x !== 0) transform += `translateX(${x}px)`; - if (hasMountX) transform += `translateX(${-props.mountX * 100}%)`; + return transforms.join(' '); +} - if (y !== 0) transform += `translateY(${y}px)`; - if (hasMountY) transform += `translateY(${-props.mountY * 100}%)`; +/** + * Fast path for transform-only updates (x, y, rotation, scale, mount). + * Skips full style rebuild but still re-evaluates viewport bounds for nodes + * with a texture source to drive lazy image load/unload during scroll. + */ +function updateTransformOnly(node: DOMNode | DOMText): void { + const transform = buildTransformCSS(node.props); + const s = node.div.style; + + if (transform.length > 0) { + s.transform = `${transform}`; + } else { + s.transform = ''; + } - if (props.rotation !== 0) transform += `rotate(${props.rotation}rad)`; + updateRenderStateIfNeeded(node); +} - if (props.scale !== 1 && props.scale != null) { - transform += `scale(${props.scale})`; - } else { - if (props.scaleX !== 1) transform += `scaleX(${props.scaleX})`; - if (props.scaleY !== 1) transform += `scaleY(${props.scaleY})`; +/** + * Recomputes the viewport bounds state for a node with a texture source and, + * if changed, triggers lazy image load or unload via updateRenderState. + */ +function updateRenderStateIfNeeded(node: DOMNode | DOMText): void { + if (!(node instanceof DOMNode) || node === node.stage.root) return; + const hasTextureSrc = nodeHasTextureSource(node); + if (hasTextureSrc && node.boundsDirty) { + const next = computeRenderStateForNode(node); + if (next != null) { + node.updateRenderState(next); } + node.boundsDirty = false; + } else if (!hasTextureSrc) { + node.boundsDirty = false; + } +} + +function updateNodeStyles(node: DOMNode | DOMText) { + const { props } = node; + + let style = `position: absolute; z-index: ${props.zIndex};`; + if (props.alpha !== 1) style += `opacity: ${props.alpha};`; + + if (props.clipping) { + style += `overflow: hidden;`; + } + + // Transform + { + const transform = buildTransformCSS(props); if (transform.length > 0) { style += `transform: ${transform};`; } - - const pivotX = props.pivotX ?? props.pivot ?? 0.5; - const pivotY = props.pivotY ?? props.pivot ?? 0.5; - if (pivotX !== 0.5 || pivotY !== 0.5) { - style += `transform-origin: ${pivotX * 100}% ${pivotY * 100}%;`; - } } // @@ -436,10 +476,9 @@ function updateNodeStyles(node: DOMNode | DOMText) { let imgStyle = ''; let hasDivBgTint = false; + let hasTint = false; if (rawImgSrc) { - needsBackgroundLayer = true; - - const hasTint = props.color !== 0xffffffff && props.color !== 0x00000000; + hasTint = props.color !== 0xffffffff && props.color !== 0x00000000; if (hasTint) { bgStyle += `background-color: ${colorToRgba(props.color)};`; @@ -465,6 +504,8 @@ function updateNodeStyles(node: DOMNode | DOMText) { 'bottom: 0', 'display: block', 'pointer-events: none', + `opacity: ${node.imageLoading ? 0 : 1}`, + 'transition: opacity 100ms linear', ]; if (props.textureOptions.resizeMode?.type) { @@ -497,7 +538,7 @@ function updateNodeStyles(node: DOMNode | DOMText) { if (supportsMixBlendMode) { imgStyleParts.push('mix-blend-mode: multiply'); } else { - imgStyleParts.push('opacity: 0.9'); + imgStyleParts.push('opacity: 1'); } } @@ -693,6 +734,16 @@ function updateNodeStyles(node: DOMNode | DOMText) { } } + // If there's still no reason to use divBg, check out tint/gradient/subtexture/radius/bgStyle + if (!needsBackgroundLayer && rawImgSrc) { + needsBackgroundLayer = + hasTint || + !!gradient || + srcPos !== null || + radiusStyle !== '' || + bgStyle !== ''; + } + style += radiusStyle; if (needsBackgroundLayer) { @@ -703,14 +754,27 @@ function updateNodeStyles(node: DOMNode | DOMText) { node.div.insertBefore(node.divBg, node.div.firstChild); } + const isSyncSubtextureUpdate = + rawImgSrc != null && + srcPos != null && + !!node.imgEl && + node.imgEl.complete && + node.imgEl.dataset.rawSrc === rawImgSrc; + if (isSyncSubtextureUpdate) { + node.imageLoading = true; + } + let bgLayerStyle = - 'position: absolute; top:0; left:0; right:0; bottom:0; z-index: -1; pointer-events: none; overflow: hidden;'; + 'position: absolute; top:0; left:0; right:0; bottom:0; z-index: -1; pointer-events: none; -webkit-clip-path: inset(0); clip-path: inset(0);'; if (bgStyle) { bgLayerStyle += bgStyle; } if (maskStyle) { bgLayerStyle += maskStyle; } + if (hasDivBgTint && srcPos != null && node.imageLoading) { + bgLayerStyle += 'opacity: 0;'; + } node.divBg.setAttribute('style', bgLayerStyle + radiusStyle); @@ -751,10 +815,19 @@ function updateNodeStyles(node: DOMNode | DOMText) { supportsObjectFit, supportsObjectPosition, ); + + // Reveal only after final fit/positioning is applied + if (node.imgEl) { + node.imageLoading = false; + node.imgEl.style.opacity = '1'; + } + node.showBackgroundLayer(); node.emit('loaded', payload); }); node.imgEl.addEventListener('error', () => { + node.imageLoading = false; + node.showBackgroundLayer(); if (node.imgEl) { node.imgEl.removeAttribute('src'); node.imgEl.style.display = 'none'; @@ -780,7 +853,7 @@ function updateNodeStyles(node: DOMNode | DOMText) { node.divBg.appendChild(node.imgEl); } - node.imgEl.setAttribute('style', imgStyle); + node.imgEl.setAttribute('style', imgStyle + radiusStyle); if (hasDivBgTint) { node.imgEl.style.visibility = 'hidden'; @@ -798,6 +871,11 @@ function updateNodeStyles(node: DOMNode | DOMText) { node.imgEl.dataset.rawSrc === rawImgSrc ) { applySubTextureScaling(node, node.imgEl, srcPos); + if (node.imageLoading) { + node.imageLoading = false; + node.imgEl.style.opacity = '1'; + node.showBackgroundLayer(); + } } if ( !srcPos && @@ -827,6 +905,134 @@ function updateNodeStyles(node: DOMNode | DOMText) { node.imgEl = undefined; } } + } else if (rawImgSrc) { + // Image directly in node.div (without divBg) when there is no tint/gradient + + // Cleanup divBg + if (node.divBg) { + node.divBg.remove(); + node.divBg = undefined; + } + + const isSyncSubtextureUpdate = + srcPos != null && + !!node.imgEl && + node.imgEl.complete && + node.imgEl.dataset.rawSrc === rawImgSrc; + if (isSyncSubtextureUpdate) { + node.imageLoading = true; + } + + if (!node.imgEl) { + node.imgEl = document.createElement('img'); + node.imgEl.alt = ''; + node.imgEl.crossOrigin = 'anonymous'; + node.imgEl.setAttribute('aria-hidden', 'true'); + node.imgEl.setAttribute('loading', 'lazy'); + node.imgEl.removeAttribute('src'); + + node.imgEl.addEventListener('load', () => { + const payload: lng.NodeTextureLoadedPayload = { + type: 'texture', + dimensions: { + w: node.imgEl!.naturalWidth, + h: node.imgEl!.naturalHeight, + }, + }; + node.imgEl!.style.display = ''; + applySubTextureScaling( + node, + node.imgEl!, + node.lazyImageSubTextureProps, + ); + + const resizeMode = (node.props.textureOptions as any)?.resizeMode; + const clipX = resizeMode?.clipX ?? 0.5; + const clipY = resizeMode?.clipY ?? 0.5; + computeLegacyObjectFit( + node, + node.imgEl!, + resizeMode, + clipX, + clipY, + node.lazyImageSubTextureProps, + supportsObjectFit, + supportsObjectPosition, + ); + + if (node.imgEl) { + node.imageLoading = false; + node.imgEl.style.opacity = '1'; + } + node.emit('loaded', payload); + }); + + node.imgEl.addEventListener('error', () => { + node.imageLoading = false; + if (node.imgEl) { + node.imgEl.removeAttribute('src'); + node.imgEl.style.display = 'none'; + node.imgEl.removeAttribute('data-rawSrc'); + } + + const failedSrc = + node.imgEl?.dataset.pendingSrc || node.lazyImagePendingSrc || ''; + + const payload: lng.NodeTextureFailedPayload = { + type: 'texture', + error: new Error(`Failed to load image: ${failedSrc}`), + }; + node.emit('failed', payload); + }); + } + + node.lazyImagePendingSrc = rawImgSrc; + node.lazyImageSubTextureProps = srcPos; + node.imgEl.dataset.pendingSrc = rawImgSrc; + + if (node.imgEl.parentElement !== node.div) { + node.div.appendChild(node.imgEl); + } + + node.imgEl.setAttribute('style', imgStyle + radiusStyle); + + if (isRenderStateInBounds(node.renderState)) { + node.applyPendingImageSrc(); + } else if (!node.imgEl.dataset.rawSrc) { + node.imgEl.removeAttribute('src'); + } + + if ( + srcPos && + node.imgEl.complete && + node.imgEl.dataset.rawSrc === rawImgSrc + ) { + applySubTextureScaling(node, node.imgEl, srcPos); + if (node.imageLoading) { + node.imageLoading = false; + node.imgEl.style.opacity = '1'; + } + } + if ( + !srcPos && + node.imgEl.complete && + (!supportsObjectFit || !supportsObjectPosition) && + node.imgEl.dataset.rawSrc === rawImgSrc + ) { + const resizeMode = (node.props.textureOptions as any)?.resizeMode; + const clipX = resizeMode?.clipX ?? 0.5; + const clipY = resizeMode?.clipY ?? 0.5; + computeLegacyObjectFit( + node, + node.imgEl, + resizeMode, + clipX, + clipY, + srcPos, + supportsObjectFit, + supportsObjectPosition, + ); + } } else { node.lazyImagePendingSrc = null; node.lazyImageSubTextureProps = null; @@ -863,20 +1069,13 @@ function updateNodeStyles(node: DOMNode | DOMText) { } } - node.div.setAttribute('style', compactString(style)); - - if (node instanceof DOMNode && node !== node.stage.root) { - const hasTextureSrc = nodeHasTextureSource(node); - if (hasTextureSrc && node.boundsDirty) { - const next = computeRenderStateForNode(node); - if (next != null) { - node.updateRenderState(next); - } - node.boundsDirty = false; - } else if (!hasTextureSrc) { - node.boundsDirty = false; - } + const newStyle = compactString(style); + if (node._lastStyleStr !== newStyle) { + node._lastStyleStr = newStyle; + node.div.setAttribute('style', newStyle); } + + updateRenderStateIfNeeded(node); } const textNodesToMeasure = new Set(); @@ -1019,6 +1218,7 @@ function scheduleUpdateDOMTextMeasurement(node: DOMText) { setTimeout(updateDOMTextMeasurements, 500); } } else { + // Fallback for devices without FontFaceSet.ready() setTimeout(updateDOMTextMeasurements, 500); } } @@ -1134,12 +1334,15 @@ export class DOMNode extends EventEmitter implements IRendererNode { divBg: HTMLElement | undefined; divBorder: HTMLElement | undefined; imgEl: HTMLImageElement | undefined; + imageLoading = false; lazyImagePendingSrc: string | null = null; lazyImageSubTextureProps: | InstanceType['props'] | null = null; boundsDirty = true; children = new Set(); + /** Cached result of the last updateNodeStyles call — avoids redundant setAttribute writes. */ + _lastStyleStr: string = ''; id = ++lastNodeId; @@ -1238,11 +1441,30 @@ export class DOMNode extends EventEmitter implements IRendererNode { } } + showBackgroundLayer() { + if (this.divBg) { + this.divBg.style.opacity = '1'; + } + } + + hideMaskedBackgroundLayer() { + if ( + this.divBg && + (this.divBg.style.maskImage || this.divBg.style.webkitMaskImage) + ) { + this.divBg.style.opacity = '0'; + } + } + applyPendingImageSrc() { if (!this.imgEl) return; const pendingSrc = this.lazyImagePendingSrc; if (!pendingSrc) return; if (this.imgEl.dataset.rawSrc === pendingSrc) return; + // Hide transient frame while source is loading and being fitted. + this.imageLoading = true; + this.imgEl.style.opacity = '0'; + this.hideMaskedBackgroundLayer(); this.imgEl.style.display = ''; this.imgEl.dataset.pendingSrc = pendingSrc; this.imgEl.src = pendingSrc; @@ -1258,7 +1480,7 @@ export class DOMNode extends EventEmitter implements IRendererNode { this.props.x = v; this.boundsDirty = true; this.markChildrenBoundsDirty(); - updateNodeStyles(this); + updateTransformOnly(this); } get y() { return this.props.y; @@ -1268,7 +1490,7 @@ export class DOMNode extends EventEmitter implements IRendererNode { this.props.y = v; this.boundsDirty = true; this.markChildrenBoundsDirty(); - updateNodeStyles(this); + updateTransformOnly(this); } get w() { return this.props.w; @@ -1435,7 +1657,7 @@ export class DOMNode extends EventEmitter implements IRendererNode { this.props.scale = v; this.boundsDirty = true; this.markChildrenBoundsDirty(); - updateNodeStyles(this); + updateTransformOnly(this); } get scaleX() { return this.props.scaleX; @@ -1445,7 +1667,7 @@ export class DOMNode extends EventEmitter implements IRendererNode { this.props.scaleX = v; this.boundsDirty = true; this.markChildrenBoundsDirty(); - updateNodeStyles(this); + updateTransformOnly(this); } get scaleY() { return this.props.scaleY; @@ -1455,7 +1677,7 @@ export class DOMNode extends EventEmitter implements IRendererNode { this.props.scaleY = v; this.boundsDirty = true; this.markChildrenBoundsDirty(); - updateNodeStyles(this); + updateTransformOnly(this); } get mount() { return this.props.mount; @@ -1465,7 +1687,7 @@ export class DOMNode extends EventEmitter implements IRendererNode { this.props.mount = v; this.boundsDirty = true; this.markChildrenBoundsDirty(); - updateNodeStyles(this); + updateTransformOnly(this); } get mountX() { return this.props.mountX; @@ -1475,7 +1697,7 @@ export class DOMNode extends EventEmitter implements IRendererNode { this.props.mountX = v; this.boundsDirty = true; this.markChildrenBoundsDirty(); - updateNodeStyles(this); + updateTransformOnly(this); } get mountY() { return this.props.mountY; @@ -1485,7 +1707,7 @@ export class DOMNode extends EventEmitter implements IRendererNode { this.props.mountY = v; this.boundsDirty = true; this.markChildrenBoundsDirty(); - updateNodeStyles(this); + updateTransformOnly(this); } get pivot() { return this.props.pivot; @@ -1525,7 +1747,7 @@ export class DOMNode extends EventEmitter implements IRendererNode { this.props.rotation = v; this.boundsDirty = true; this.markChildrenBoundsDirty(); - updateNodeStyles(this); + updateTransformOnly(this); } get rtt() { return this.props.rtt; diff --git a/src/core/dom-renderer/domRendererTypes.ts b/src/core/dom-renderer/domRendererTypes.ts index 73cf833..6d62a6d 100644 --- a/src/core/dom-renderer/domRendererTypes.ts +++ b/src/core/dom-renderer/domRendererTypes.ts @@ -83,10 +83,8 @@ export interface IRendererNodeShaded extends EventEmitter { } /** Based on {@link lng.INodeProps} */ -export interface IRendererNodeProps extends Omit< - lng.INodeProps, - 'shader' | 'parent' -> { +export interface IRendererNodeProps + extends Omit { shader: IRendererShader | null; parent: IRendererNode | null; } @@ -99,10 +97,8 @@ export interface IRendererNode extends IRendererNodeShaded, IRendererNodeProps { } /** Based on {@link lng.ITextNodeProps} */ -export interface IRendererTextNodeProps extends Omit< - lng.ITextNodeProps, - 'shader' | 'parent' -> { +export interface IRendererTextNodeProps + extends Omit { shader: IRendererShader | null; parent: IRendererNode | null; fontWeight?: string; @@ -111,7 +107,8 @@ export interface IRendererTextNodeProps extends Omit< /** Based on {@link lng.ITextNode} */ export interface IRendererTextNode - extends IRendererNodeShaded, IRendererTextNodeProps { + extends IRendererNodeShaded, + IRendererTextNodeProps { div?: HTMLElement; props: IRendererTextNodeProps; renderState: lng.CoreNodeRenderState; diff --git a/src/core/intrinsicTypes.ts b/src/core/intrinsicTypes.ts index 6f6cd6f..7ee5a85 100644 --- a/src/core/intrinsicTypes.ts +++ b/src/core/intrinsicTypes.ts @@ -24,18 +24,16 @@ export type AddColorString = { [K in keyof T]: K extends `color${string}` ? string | number : T[K]; }; -export interface BorderStyleObject extends AddColorString< - Partial -> { +export interface BorderStyleObject + extends AddColorString> { width?: number | [number, number, number, number]; gap?: number; align?: 'inside' | 'outside' | 'center'; fill?: number | string; } -export interface SingleBorderStyleObject extends AddColorString< - Partial -> { +export interface SingleBorderStyleObject + extends AddColorString> { width?: number; w?: number; gap?: number; @@ -106,8 +104,7 @@ type CleanElementNode = NewOmit< >; /** Node text, children of a ElementNode of type TextNode */ export interface ElementText - extends - NewOmit< + extends NewOmit< ElementNode, '_type' | 'parent' | 'children' | 'src' | 'scale' | 'fontFamily' >, @@ -120,8 +117,7 @@ export interface ElementText } export interface NodeProps - extends - RendererNode, + extends RendererNode, EventHandlers, EventHandlers, FocusNode, @@ -151,8 +147,7 @@ export interface NodeStyles extends NewOmit { } export interface TextProps - extends - RendererText, + extends RendererText, Partial< NewOmit< CleanElementNode, diff --git a/tests/tsconfig.json b/tests/tsconfig.json index 4c5da66..49c08a0 100644 --- a/tests/tsconfig.json +++ b/tests/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "types": ["node"] + "types": ["vitest/globals"] }, "include": ["**/*", "../src/**/*"] }