SVG: Allow setting filters using JSX (#5449)
This commit is contained in:
parent
af472d0fd4
commit
8dc935d0b8
@ -86,6 +86,7 @@ import GiveawayModal from './premium/GiveawayModal.async';
|
||||
import PremiumMainModal from './premium/PremiumMainModal.async';
|
||||
import StarsGiftingPickerModal from './premium/StarsGiftingPickerModal.async';
|
||||
import SafeLinkModal from './SafeLinkModal.async';
|
||||
import SvgController from './SvgController';
|
||||
import ConfettiContainer from './visualEffects/ConfettiContainer';
|
||||
import SnapEffectContainer from './visualEffects/SnapEffectContainer';
|
||||
import WaveContainer from './visualEffects/WaveContainer';
|
||||
@ -587,6 +588,7 @@ const Main = ({
|
||||
<DeleteFolderDialog folder={deleteFolderDialog} />
|
||||
<ReactionPicker isOpen={isReactionPickerOpen} />
|
||||
<DeleteMessageModal isOpen={isDeleteMessageModalOpen} />
|
||||
<SvgController />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
45
src/components/main/SvgController.tsx
Normal file
45
src/components/main/SvgController.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import React, { memo, useEffect } from '../../lib/teact/teact';
|
||||
|
||||
import { createCallbackManager } from '../../util/callbacks';
|
||||
import generateUniqueId from '../../util/generateUniqueId';
|
||||
|
||||
import useForceUpdate from '../../hooks/useForceUpdate';
|
||||
|
||||
import Portal from '../ui/Portal';
|
||||
|
||||
const DEFINITION_MAP = new Map<string, React.ReactElement>();
|
||||
const CALLBACK_MANAGER = createCallbackManager();
|
||||
|
||||
const SvgController = () => {
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
useEffect(() => {
|
||||
return CALLBACK_MANAGER.addCallback(forceUpdate);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<svg width="0" height="0" viewBox="0 0 1 1" className="svg-definitions">
|
||||
<defs>
|
||||
{Array.from(DEFINITION_MAP.values())}
|
||||
</defs>
|
||||
</svg>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(SvgController);
|
||||
|
||||
export function addSvgDefinition(element: React.ReactElement, id?: string) {
|
||||
id ??= generateUniqueId();
|
||||
element.props.id = id;
|
||||
|
||||
DEFINITION_MAP.set(element.props.id, element);
|
||||
CALLBACK_MANAGER.runCallbacks();
|
||||
return id;
|
||||
}
|
||||
|
||||
export function removeSvgDefinition(id: string) {
|
||||
DEFINITION_MAP.delete(id);
|
||||
CALLBACK_MANAGER.runCallbacks();
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
import React, { memo } from '../../../lib/teact/teact';
|
||||
import { getGlobal } from '../../../global';
|
||||
|
||||
import { SNAP_EFFECT_CONTAINER_ID, SNAP_EFFECT_ID } from '../../../config';
|
||||
import { SNAP_EFFECT_CONTAINER_ID, SNAP_EFFECT_ID, SVG_NAMESPACE } from '../../../config';
|
||||
import { selectCanAnimateSnapEffect } from '../../../global/selectors';
|
||||
import jsxToHtml from '../../../util/element/jsxToHtml';
|
||||
import generateUniqueId from '../../../util/generateUniqueId';
|
||||
import { SVG_NAMESPACE } from '../../../util/svgController';
|
||||
|
||||
import styles from './SnapEffectContainer.module.scss';
|
||||
|
||||
@ -39,29 +39,25 @@ export function animateSnap(element: HTMLElement) {
|
||||
const seed = Math.floor(Date.now() / 1000);
|
||||
const filterId = `${SNAP_EFFECT_ID}-${generateUniqueId()}`;
|
||||
|
||||
const svg = document.createElementNS(SVG_NAMESPACE, 'svg');
|
||||
svg.setAttribute('class', styles.ghost);
|
||||
svg.setAttribute('width', `${width}px`);
|
||||
svg.setAttribute('height', `${height}px`);
|
||||
svg.setAttribute('style', `left: ${x}px; top: ${y}px;`);
|
||||
svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
|
||||
const component = (
|
||||
<svg
|
||||
className={styles.ghost}
|
||||
width={width}
|
||||
height={height}
|
||||
style={`left: ${x}px; top: ${y}px;`}
|
||||
viewBox={`0 0 ${width} ${height}`}
|
||||
>
|
||||
<defs>
|
||||
{createFilter(filterId, Math.min(width, height), seed)}
|
||||
</defs>
|
||||
<g filter={`url(#${filterId})`}>
|
||||
<foreignObject className={styles.elementContainer} width={width} height={height} />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const defs = document.createElementNS(SVG_NAMESPACE, 'defs');
|
||||
svg.appendChild(defs);
|
||||
|
||||
const filter = createFilter(Math.min(width, height), seed);
|
||||
filter.setAttribute('id', filterId);
|
||||
defs.appendChild(filter);
|
||||
|
||||
const g = document.createElementNS(SVG_NAMESPACE, 'g');
|
||||
g.setAttribute('filter', `url(#${filterId})`);
|
||||
svg.appendChild(g);
|
||||
|
||||
const foreignObject = document.createElementNS(SVG_NAMESPACE, 'foreignObject');
|
||||
foreignObject.setAttribute('class', styles.elementContainer);
|
||||
foreignObject.setAttribute('width', `${width}px`);
|
||||
foreignObject.setAttribute('height', `${height}px`);
|
||||
g.appendChild(foreignObject);
|
||||
const svg = jsxToHtml(component)[0] as HTMLElement;
|
||||
const foreignObject = svg.querySelector('foreignObject')!;
|
||||
|
||||
const computedStyle = window.getComputedStyle(element);
|
||||
const clone = element.cloneNode(true) as HTMLElement;
|
||||
@ -83,90 +79,45 @@ export function animateSnap(element: HTMLElement) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function createFilter(smallestSide: number, baseSeed: number = 42) {
|
||||
const filter = document.createElementNS(SVG_NAMESPACE, 'filter');
|
||||
filter.setAttribute('x', '-150%');
|
||||
filter.setAttribute('y', '-150%');
|
||||
filter.setAttribute('width', '400%');
|
||||
filter.setAttribute('height', '400%');
|
||||
filter.setAttribute('color-interpolation-filters', 'sRGB');
|
||||
|
||||
const feTurbulence = document.createElementNS(SVG_NAMESPACE, 'feTurbulence');
|
||||
feTurbulence.setAttribute('type', 'fractalNoise');
|
||||
feTurbulence.setAttribute('baseFrequency', '0.5');
|
||||
feTurbulence.setAttribute('numOctaves', '1');
|
||||
feTurbulence.setAttribute('result', 'dustNoise');
|
||||
feTurbulence.setAttribute('seed', baseSeed.toString());
|
||||
filter.appendChild(feTurbulence);
|
||||
|
||||
const feComponentTransfer = document.createElementNS(SVG_NAMESPACE, 'feComponentTransfer');
|
||||
feComponentTransfer.setAttribute('in', 'dustNoise');
|
||||
feComponentTransfer.setAttribute('result', 'dustNoiseMask');
|
||||
filter.appendChild(feComponentTransfer);
|
||||
|
||||
const feFuncA = document.createElementNS(SVG_NAMESPACE, 'feFuncA');
|
||||
feFuncA.setAttribute('type', 'linear');
|
||||
feFuncA.setAttribute('slope', '5');
|
||||
feFuncA.setAttribute('intercept', '0');
|
||||
feComponentTransfer.appendChild(feFuncA);
|
||||
|
||||
const feFuncAAnimate = document.createElementNS(SVG_NAMESPACE, 'animate');
|
||||
feFuncAAnimate.setAttribute('attributeName', 'slope');
|
||||
feFuncAAnimate.setAttribute('values', '5; 2; 1; 0');
|
||||
feFuncAAnimate.setAttribute('dur', `${DURATION}ms`);
|
||||
feFuncAAnimate.setAttribute('fill', 'freeze');
|
||||
feFuncA.appendChild(feFuncAAnimate);
|
||||
|
||||
const feComposite = document.createElementNS(SVG_NAMESPACE, 'feComposite');
|
||||
feComposite.setAttribute('in', 'SourceGraphic');
|
||||
feComposite.setAttribute('in2', 'dustNoiseMask');
|
||||
feComposite.setAttribute('operator', 'in');
|
||||
feComposite.setAttribute('result', 'dustySource');
|
||||
filter.appendChild(feComposite);
|
||||
|
||||
const feTurbulence2 = document.createElementNS(SVG_NAMESPACE, 'feTurbulence');
|
||||
feTurbulence2.setAttribute('type', 'fractalNoise');
|
||||
feTurbulence2.setAttribute('baseFrequency', '0.015');
|
||||
feTurbulence2.setAttribute('numOctaves', '1');
|
||||
feTurbulence2.setAttribute('result', 'displacementNoice1');
|
||||
feTurbulence2.setAttribute('seed', (baseSeed + 1).toString());
|
||||
filter.appendChild(feTurbulence2);
|
||||
|
||||
const feTurbulence3 = document.createElementNS(SVG_NAMESPACE, 'feTurbulence');
|
||||
feTurbulence3.setAttribute('type', 'fractalNoise');
|
||||
feTurbulence3.setAttribute('baseFrequency', '1');
|
||||
feTurbulence3.setAttribute('numOctaves', '2');
|
||||
feTurbulence3.setAttribute('result', 'displacementNoice2');
|
||||
feTurbulence3.setAttribute('seed', (baseSeed + 2).toString());
|
||||
filter.appendChild(feTurbulence3);
|
||||
|
||||
const feMerge = document.createElementNS(SVG_NAMESPACE, 'feMerge');
|
||||
feMerge.setAttribute('result', 'combinedNoise');
|
||||
filter.appendChild(feMerge);
|
||||
|
||||
const feMergeNode1 = document.createElementNS(SVG_NAMESPACE, 'feMergeNode');
|
||||
feMergeNode1.setAttribute('in', 'displacementNoice1');
|
||||
feMerge.appendChild(feMergeNode1);
|
||||
|
||||
const feMergeNode2 = document.createElementNS(SVG_NAMESPACE, 'feMergeNode');
|
||||
feMergeNode2.setAttribute('in', 'displacementNoice2');
|
||||
feMerge.appendChild(feMergeNode2);
|
||||
|
||||
const feDisplacementMap = document.createElementNS(SVG_NAMESPACE, 'feDisplacementMap');
|
||||
feDisplacementMap.setAttribute('in', 'dustySource');
|
||||
feDisplacementMap.setAttribute('in2', 'combinedNoise');
|
||||
feDisplacementMap.setAttribute('scale', '0');
|
||||
|
||||
feDisplacementMap.setAttribute('xChannelSelector', 'R');
|
||||
feDisplacementMap.setAttribute('yChannelSelector', 'G');
|
||||
filter.appendChild(feDisplacementMap);
|
||||
|
||||
const feDisplacementMapAnimate = document.createElementNS(SVG_NAMESPACE, 'animate');
|
||||
feDisplacementMapAnimate.setAttribute('attributeName', 'scale');
|
||||
feDisplacementMapAnimate.setAttribute('values', `0; ${smallestSide * 3}`);
|
||||
feDisplacementMapAnimate.setAttribute('dur', `${DURATION}ms`);
|
||||
feDisplacementMapAnimate.setAttribute('fill', 'freeze');
|
||||
feDisplacementMap.appendChild(feDisplacementMapAnimate);
|
||||
|
||||
return filter;
|
||||
function createFilter(id: string, smallestSide: number, baseSeed: number = 42) {
|
||||
return (
|
||||
<filter
|
||||
xmlns={SVG_NAMESPACE}
|
||||
id={id}
|
||||
x="-150%"
|
||||
y="-150%"
|
||||
width="400%"
|
||||
height="400%"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feTurbulence type="fractalNoise" baseFrequency="0.5" numOctaves="1" result="dustNoise" seed={baseSeed} />
|
||||
<feComponentTransfer in="dustNoise" result="dustNoiseMask">
|
||||
<feFuncA type="linear" slope="5" intercept="0">
|
||||
<animate attributeName="slope" values="5; 2; 1; 0" dur={`${DURATION}ms`} fill="freeze" />
|
||||
</feFuncA>
|
||||
</feComponentTransfer>
|
||||
<feComposite in="SourceGraphic" in2="dustNoiseMask" operator="in" result="dustySource" />
|
||||
<feTurbulence
|
||||
type="fractalNoise"
|
||||
baseFrequency="0.015"
|
||||
numOctaves="1"
|
||||
result="displacementNoise1"
|
||||
seed={baseSeed + 1}
|
||||
/>
|
||||
<feTurbulence
|
||||
type="fractalNoise"
|
||||
baseFrequency="1"
|
||||
numOctaves="2"
|
||||
result="displacementNoise2"
|
||||
seed={baseSeed + 2}
|
||||
/>
|
||||
<feMerge result="combinedNoise">
|
||||
<feMergeNode in="displacementNoise1" />
|
||||
<feMergeNode in="displacementNoise2" />
|
||||
</feMerge>
|
||||
<feDisplacementMap in="dustySource" in2="combinedNoise" scale="0" xChannelSelector="R" yChannelSelector="G">
|
||||
<animate attributeName="scale" values={`0; ${smallestSide * 3}`} dur={`${DURATION}ms`} fill="freeze" />
|
||||
</feDisplacementMap>
|
||||
</filter>
|
||||
);
|
||||
}
|
||||
|
||||
@ -5,14 +5,16 @@ import { withGlobal } from '../../../global';
|
||||
|
||||
import type { TabState } from '../../../global/types';
|
||||
|
||||
import { SVG_NAMESPACE } from '../../../config';
|
||||
import { selectTabState } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import buildStyle from '../../../util/buildStyle';
|
||||
import { addSvgDefinition, removeSvgDefinition, SVG_NAMESPACE } from '../../../util/svgController';
|
||||
import windowSize from '../../../util/windowSize';
|
||||
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import { addSvgDefinition, removeSvgDefinition } from '../SvgController';
|
||||
|
||||
import styles from './WaveContainer.module.scss';
|
||||
|
||||
import waveRipple from '../../../assets/wave_ripple.svg';
|
||||
@ -62,26 +64,19 @@ const WaveContainer = ({ waveInfo }: StateProps) => {
|
||||
}, [waveInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
const filter = document.createElementNS(SVG_NAMESPACE, 'filter');
|
||||
filter.setAttribute('x', '0');
|
||||
filter.setAttribute('y', '0');
|
||||
filter.setAttribute('width', '1');
|
||||
filter.setAttribute('height', '1');
|
||||
filter.setAttribute('color-interpolation-filters', 'sRGB');
|
||||
addSvgDefinition(filter, FILTER_ID);
|
||||
|
||||
const feImage = document.createElementNS(SVG_NAMESPACE, 'feImage');
|
||||
feImage.setAttribute('href', waveRipple);
|
||||
feImage.setAttribute('result', 'waveImage');
|
||||
filter.appendChild(feImage);
|
||||
|
||||
const feDisplacementMap = document.createElementNS(SVG_NAMESPACE, 'feDisplacementMap');
|
||||
feDisplacementMap.setAttribute('in', 'SourceGraphic');
|
||||
feDisplacementMap.setAttribute('in2', 'waveImage');
|
||||
feDisplacementMap.setAttribute('scale', FILTER_SCALE);
|
||||
feDisplacementMap.setAttribute('xChannelSelector', 'R');
|
||||
feDisplacementMap.setAttribute('yChannelSelector', 'B');
|
||||
filter.appendChild(feDisplacementMap);
|
||||
addSvgDefinition(
|
||||
<filter x="0" y="0" width="1" height="1" color-interpolation-filters="sRGB" xmlns={SVG_NAMESPACE}>
|
||||
<feImage href={waveRipple} result="waveImage" />
|
||||
<feDisplacementMap
|
||||
in="SourceGraphic"
|
||||
in2="waveImage"
|
||||
scale={FILTER_SCALE}
|
||||
xChannelSelector="R"
|
||||
yChannelSelector="B"
|
||||
/>
|
||||
</filter>,
|
||||
FILTER_ID,
|
||||
);
|
||||
|
||||
return () => {
|
||||
removeSvgDefinition(FILTER_ID);
|
||||
|
||||
@ -227,6 +227,8 @@ export const SLIDE_TRANSITION_DURATION = 450;
|
||||
export const BIRTHDAY_NUMBERS_SET = 'FestiveFontEmoji';
|
||||
export const RESTRICTED_EMOJI_SET = 'RestrictedEmoji';
|
||||
|
||||
export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
|
||||
|
||||
export const VIDEO_WEBM_TYPE = 'video/webm';
|
||||
export const GIF_MIME_TYPE = 'image/gif';
|
||||
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { useEffect } from '../../lib/teact/teact';
|
||||
import React, { useEffect } from '../../lib/teact/teact';
|
||||
|
||||
import { addSvgDefinition, removeSvgDefinition, SVG_NAMESPACE } from '../../util/svgController';
|
||||
import { SVG_NAMESPACE } from '../../config';
|
||||
import { hexToRgb } from '../../util/switchTheme';
|
||||
|
||||
import { addSvgDefinition, removeSvgDefinition } from '../../components/main/SvgController';
|
||||
|
||||
const SVG_MAP = new Map<string, SvgColorFilter>();
|
||||
|
||||
class SvgColorFilter {
|
||||
@ -13,20 +15,16 @@ class SvgColorFilter {
|
||||
constructor(public color: string) {
|
||||
this.filterId = `color-filter-${color.slice(1)}`;
|
||||
|
||||
const filter = document.createElementNS(SVG_NAMESPACE, 'filter');
|
||||
filter.setAttribute('color-interpolation-filters', 'sRGB');
|
||||
addSvgDefinition(filter, this.filterId);
|
||||
|
||||
const feColorMatrix = document.createElementNS(SVG_NAMESPACE, 'feColorMatrix');
|
||||
feColorMatrix.setAttribute('type', 'matrix');
|
||||
|
||||
const rgbColor = hexToRgb(color);
|
||||
feColorMatrix.setAttribute(
|
||||
'values',
|
||||
`0 0 0 0 ${rgbColor.r / 255} 0 0 0 0 ${rgbColor.g / 255} 0 0 0 0 ${rgbColor.b / 255} 0 0 0 1 0`,
|
||||
addSvgDefinition(
|
||||
<filter color-interpolation-filters="sRGB" xmlns={SVG_NAMESPACE}>
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values={`0 0 0 0 ${rgbColor.r / 255} 0 0 0 0 ${rgbColor.g / 255} 0 0 0 0 ${rgbColor.b / 255} 0 0 0 1 0`}
|
||||
/>
|
||||
</filter>,
|
||||
this.filterId,
|
||||
);
|
||||
|
||||
filter.appendChild(feColorMatrix);
|
||||
}
|
||||
|
||||
public getFilterId() {
|
||||
@ -39,6 +39,9 @@ type CurrentContext = Record<string, Signal<unknown>>;
|
||||
|
||||
type DOMElement = HTMLElement | SVGElement;
|
||||
|
||||
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
|
||||
const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
|
||||
|
||||
const FILTERED_ATTRIBUTES = new Set(['key', 'ref', 'teactFastList', 'teactOrderKey']);
|
||||
const HTML_ATTRIBUTES = new Set(['dir', 'role', 'form']);
|
||||
const CONTROLLABLE_TAGS = ['INPUT', 'TEXTAREA', 'SELECT'];
|
||||
@ -89,11 +92,11 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
nextSibling?: ChildNode;
|
||||
forceMoveToEnd?: boolean;
|
||||
fragment?: DocumentFragment;
|
||||
isSvg?: true;
|
||||
namespace?: string;
|
||||
} = {},
|
||||
): T {
|
||||
const { skipComponentUpdate, fragment } = options;
|
||||
let { nextSibling, isSvg } = options;
|
||||
let { nextSibling, namespace } = options;
|
||||
|
||||
const isCurrentComponent = $current?.type === VirtualType.Component;
|
||||
const isNewComponent = $new?.type === VirtualType.Component;
|
||||
@ -102,8 +105,9 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
const isCurrentFragment = !isCurrentComponent && $current?.type === VirtualType.Fragment;
|
||||
const isNewFragment = !isNewComponent && $new?.type === VirtualType.Fragment;
|
||||
|
||||
if (!isSvg && $new?.type === VirtualType.Tag && $new.tag === 'svg') {
|
||||
isSvg = true;
|
||||
if ($new?.type === VirtualType.Tag) {
|
||||
if ($new.tag === 'svg') namespace = SVG_NAMESPACE;
|
||||
if ($new.props.xmlns) namespace = $new.props.xmlns;
|
||||
}
|
||||
|
||||
if (
|
||||
@ -144,7 +148,7 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
}
|
||||
|
||||
mountChildren(parentEl, $new as VirtualElementComponent | VirtualElementFragment, currentContext, {
|
||||
nextSibling, fragment, isSvg,
|
||||
nextSibling, fragment, namespace,
|
||||
});
|
||||
} else {
|
||||
const canSetTextContent = !fragment
|
||||
@ -157,7 +161,7 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
parentEl.textContent = $newAsReal.value;
|
||||
$newAsReal.target = parentEl.firstChild!;
|
||||
} else {
|
||||
const node = createNode($newAsReal, currentContext, isSvg);
|
||||
const node = createNode($newAsReal, currentContext, namespace);
|
||||
$newAsReal.target = node;
|
||||
insertBefore(fragment || parentEl, node, nextSibling);
|
||||
|
||||
@ -184,10 +188,10 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
|
||||
remount(parentEl, $current, currentContext, undefined);
|
||||
mountChildren(parentEl, $new as VirtualElementComponent | VirtualElementFragment, currentContext, {
|
||||
nextSibling, fragment, isSvg,
|
||||
nextSibling, fragment, namespace,
|
||||
});
|
||||
} else {
|
||||
const node = createNode($newAsReal, currentContext, isSvg);
|
||||
const node = createNode($newAsReal, currentContext, namespace);
|
||||
$newAsReal.target = node;
|
||||
remount(parentEl, $current, currentContext, node, nextSibling);
|
||||
|
||||
@ -226,8 +230,10 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
insertBefore(parentEl, currentTarget, nextSibling);
|
||||
}
|
||||
|
||||
updateAttributes($current, $newAsTag, currentTarget as DOMElement, isSvg);
|
||||
renderChildren($current, $newAsTag, currentContext, currentTarget as DOMElement, undefined, undefined, isSvg);
|
||||
updateAttributes($current, $newAsTag, currentTarget as DOMElement, namespace);
|
||||
renderChildren(
|
||||
$current, $newAsTag, currentContext, currentTarget as DOMElement, undefined, undefined, namespace,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -290,7 +296,7 @@ function mountChildren(
|
||||
options: {
|
||||
nextSibling?: ChildNode;
|
||||
fragment?: DocumentFragment;
|
||||
isSvg?: true;
|
||||
namespace?: string;
|
||||
},
|
||||
) {
|
||||
const { children } = $element;
|
||||
@ -311,7 +317,7 @@ function unmountChildren(
|
||||
}
|
||||
}
|
||||
|
||||
function createNode($element: VirtualElementReal, currentContext: CurrentContext, isSvg?: true): Node {
|
||||
function createNode($element: VirtualElementReal, currentContext: CurrentContext, namespace = HTML_NAMESPACE): Node {
|
||||
if ($element.type === VirtualType.Empty) {
|
||||
return document.createTextNode('');
|
||||
}
|
||||
@ -321,7 +327,7 @@ function createNode($element: VirtualElementReal, currentContext: CurrentContext
|
||||
}
|
||||
|
||||
const { tag, props, children } = $element;
|
||||
const element = isSvg ? document.createElementNS('http://www.w3.org/2000/svg', tag) : document.createElement(tag);
|
||||
const element = document.createElementNS(namespace, tag) as DOMElement;
|
||||
|
||||
processControlled(tag, props);
|
||||
|
||||
@ -330,7 +336,7 @@ function createNode($element: VirtualElementReal, currentContext: CurrentContext
|
||||
if (!props.hasOwnProperty(key)) continue;
|
||||
|
||||
if (props[key] !== undefined) {
|
||||
setAttribute(element, key, props[key], isSvg);
|
||||
setAttribute(element, key, props[key], namespace);
|
||||
}
|
||||
}
|
||||
|
||||
@ -338,7 +344,7 @@ function createNode($element: VirtualElementReal, currentContext: CurrentContext
|
||||
|
||||
for (let i = 0, l = children.length; i < l; i++) {
|
||||
const $child = children[i];
|
||||
const $renderedChild = renderWithVirtual(element, undefined, $child, $element, currentContext, i, { isSvg });
|
||||
const $renderedChild = renderWithVirtual(element, undefined, $child, $element, currentContext, i, { namespace });
|
||||
if ($renderedChild !== $child) {
|
||||
children[i] = $renderedChild;
|
||||
}
|
||||
@ -424,7 +430,7 @@ function renderChildren(
|
||||
currentEl: DOMElement,
|
||||
nextSibling?: ChildNode,
|
||||
forceMoveToEnd = false,
|
||||
isSvg?: true,
|
||||
namespace?: string,
|
||||
) {
|
||||
if (DEBUG) {
|
||||
DEBUG_checkKeyUniqueness($new.children);
|
||||
@ -456,7 +462,7 @@ function renderChildren(
|
||||
$new,
|
||||
currentContext,
|
||||
i,
|
||||
i >= currentChildrenLength ? { fragment, isSvg } : { nextSibling, forceMoveToEnd, isSvg },
|
||||
i >= currentChildrenLength ? { fragment, namespace } : { nextSibling, forceMoveToEnd, namespace },
|
||||
);
|
||||
|
||||
if ($renderedChild && $renderedChild !== newChildren[i]) {
|
||||
@ -691,7 +697,9 @@ function processUncontrolledOnMount(element: DOMElement, props: AnyLiteral) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateAttributes($current: VirtualElementTag, $new: VirtualElementTag, element: DOMElement, isSvg?: true) {
|
||||
function updateAttributes(
|
||||
$current: VirtualElementTag, $new: VirtualElementTag, element: DOMElement, namespace?: string,
|
||||
) {
|
||||
processControlled(element.tagName, $new.props);
|
||||
|
||||
const currentEntries = Object.entries($current.props);
|
||||
@ -715,14 +723,14 @@ function updateAttributes($current: VirtualElementTag, $new: VirtualElementTag,
|
||||
const currentValue = $current.props[key];
|
||||
|
||||
if (newValue !== undefined && newValue !== currentValue) {
|
||||
setAttribute(element, key, newValue, isSvg);
|
||||
setAttribute(element, key, newValue, namespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setAttribute(element: DOMElement, key: string, value: any, isSvg?: true) {
|
||||
function setAttribute(element: DOMElement, key: string, value: any, namespace?: string) {
|
||||
if (key === 'className') {
|
||||
updateClassName(element, value, isSvg);
|
||||
updateClassName(element, value, namespace);
|
||||
} else if (key === 'value') {
|
||||
const inputEl = element as HTMLInputElement;
|
||||
|
||||
@ -749,7 +757,9 @@ function setAttribute(element: DOMElement, key: string, value: any, isSvg?: true
|
||||
element.innerHTML = value.__html;
|
||||
} else if (key.startsWith('on')) {
|
||||
addEventListener(element, key, value, key.endsWith('Capture'));
|
||||
} else if (isSvg || key.startsWith('data-') || key.startsWith('aria-') || HTML_ATTRIBUTES.has(key)) {
|
||||
} else if (
|
||||
namespace === SVG_NAMESPACE || key.startsWith('data-') || key.startsWith('aria-') || HTML_ATTRIBUTES.has(key)
|
||||
) {
|
||||
element.setAttribute(key, value);
|
||||
} else if (!FILTERED_ATTRIBUTES.has(key)) {
|
||||
(element as any)[MAPPED_ATTRIBUTES[key] || key] = value;
|
||||
@ -772,8 +782,8 @@ function removeAttribute(element: DOMElement, key: string, value: any) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateClassName(element: DOMElement, value: string, isSvg?: true) {
|
||||
if (isSvg) {
|
||||
function updateClassName(element: DOMElement, value: string, namespace?: string) {
|
||||
if (namespace === SVG_NAMESPACE) {
|
||||
element.setAttribute('class', value);
|
||||
return;
|
||||
}
|
||||
|
||||
12
src/util/element/jsxToHtml.ts
Normal file
12
src/util/element/jsxToHtml.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import type { VirtualElement } from '../../lib/teact/teact';
|
||||
import TeactDOM from '../../lib/teact/teact-dom';
|
||||
|
||||
export default function jsxToHtml(jsx: VirtualElement) {
|
||||
const fragment = document.createElement('div');
|
||||
TeactDOM.render(jsx, fragment);
|
||||
|
||||
const children = Array.from(fragment.children);
|
||||
TeactDOM.render(undefined, fragment);
|
||||
|
||||
return children;
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
import generateUniqueId from './generateUniqueId';
|
||||
|
||||
export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
|
||||
|
||||
const CONTAINER = document.createElementNS(SVG_NAMESPACE, 'svg');
|
||||
CONTAINER.setAttribute('width', '0');
|
||||
CONTAINER.setAttribute('height', '0');
|
||||
CONTAINER.setAttribute('viewBox', '0 0 1 1');
|
||||
CONTAINER.classList.add('svg-definitions');
|
||||
document.body.appendChild(CONTAINER);
|
||||
|
||||
const DEFS = document.createElementNS(SVG_NAMESPACE, 'defs');
|
||||
CONTAINER.appendChild(DEFS);
|
||||
|
||||
const DEFINITION_MAP = new Map<string, SVGElement>();
|
||||
|
||||
export function addSvgDefinition(element: SVGElement, id?: string) {
|
||||
id ??= generateUniqueId();
|
||||
element.id = id;
|
||||
|
||||
DEFS.appendChild(element);
|
||||
DEFINITION_MAP.set(element.id, element);
|
||||
return id;
|
||||
}
|
||||
|
||||
export function removeSvgDefinition(id: string) {
|
||||
const element = DEFINITION_MAP.get(id);
|
||||
if (element) {
|
||||
element.remove();
|
||||
DEFINITION_MAP.delete(id);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user