Teact: Support SVG (#3593)
This commit is contained in:
parent
9b967cce31
commit
46d9278900
@ -28,7 +28,7 @@ import LockScreen from './main/LockScreen.async';
|
|||||||
import AppInactive from './main/AppInactive';
|
import AppInactive from './main/AppInactive';
|
||||||
import Transition from './ui/Transition';
|
import Transition from './ui/Transition';
|
||||||
import UiLoader from './common/UiLoader';
|
import UiLoader from './common/UiLoader';
|
||||||
// import Test from './test/TestUpdateRef';
|
// import Test from './test/TestSvg';
|
||||||
|
|
||||||
import styles from './App.module.scss';
|
import styles from './App.module.scss';
|
||||||
|
|
||||||
|
|||||||
51
src/components/test/TestSvg.tsx
Normal file
51
src/components/test/TestSvg.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import React, { useState } from '../../lib/teact/teact';
|
||||||
|
|
||||||
|
export function App() {
|
||||||
|
const [stateValue, setStateValue] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="App"
|
||||||
|
onClick={() => {
|
||||||
|
setStateValue((current) => !current);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="150" version="1.1">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
|
<stop offset="0%" style="stop-color:rgb(0,255,0);stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:rgb(0,0,255);stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
{stateValue && (
|
||||||
|
<circle
|
||||||
|
cx="75"
|
||||||
|
cy="75"
|
||||||
|
r="50"
|
||||||
|
stroke-dasharray="85 20"
|
||||||
|
stroke="#dddddd"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="10"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<circle
|
||||||
|
cx="75"
|
||||||
|
cy="75"
|
||||||
|
r="50"
|
||||||
|
className="shared-canvas-container"
|
||||||
|
stroke-dashoffset={stateValue ? '140' : '0'}
|
||||||
|
stroke-dasharray={stateValue ? '85 160' : '85 1000'}
|
||||||
|
stroke="url(#grad1)"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="10"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
@ -1,15 +1,15 @@
|
|||||||
import { DEBUG } from '../../config';
|
import { DEBUG } from '../../config';
|
||||||
|
|
||||||
type Handler = (e: Event) => void;
|
type Handler = (e: Event) => void;
|
||||||
type DelegationRegistry = Map<HTMLElement, Handler>;
|
type DelegationRegistry = Map<Element, Handler>;
|
||||||
|
|
||||||
const NON_BUBBLEABLE_EVENTS = new Set(['scroll', 'mouseenter', 'mouseleave', 'load']);
|
const NON_BUBBLEABLE_EVENTS = new Set(['scroll', 'mouseenter', 'mouseleave', 'load']);
|
||||||
|
|
||||||
const documentEventCounters: Record<string, number> = {};
|
const documentEventCounters: Record<string, number> = {};
|
||||||
const delegationRegistryByEventType: Record<string, DelegationRegistry> = {};
|
const delegationRegistryByEventType: Record<string, DelegationRegistry> = {};
|
||||||
const delegatedEventTypesByElement = new Map<HTMLElement, Set<string>>();
|
const delegatedEventTypesByElement = new Map<Element, Set<string>>();
|
||||||
|
|
||||||
export function addEventListener(element: HTMLElement, propName: string, handler: Handler, asCapture = false) {
|
export function addEventListener(element: Element, propName: string, handler: Handler, asCapture = false) {
|
||||||
const eventType = resolveEventType(propName, element);
|
const eventType = resolveEventType(propName, element);
|
||||||
if (canUseEventDelegation(eventType, element, asCapture)) {
|
if (canUseEventDelegation(eventType, element, asCapture)) {
|
||||||
addDelegatedListener(eventType, element, handler);
|
addDelegatedListener(eventType, element, handler);
|
||||||
@ -18,7 +18,7 @@ export function addEventListener(element: HTMLElement, propName: string, handler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeEventListener(element: HTMLElement, propName: string, handler: Handler, asCapture = false) {
|
export function removeEventListener(element: Element, propName: string, handler: Handler, asCapture = false) {
|
||||||
const eventType = resolveEventType(propName, element);
|
const eventType = resolveEventType(propName, element);
|
||||||
if (canUseEventDelegation(eventType, element, asCapture)) {
|
if (canUseEventDelegation(eventType, element, asCapture)) {
|
||||||
removeDelegatedListener(eventType, element);
|
removeDelegatedListener(eventType, element);
|
||||||
@ -27,7 +27,7 @@ export function removeEventListener(element: HTMLElement, propName: string, hand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveEventType(propName: string, element: HTMLElement) {
|
function resolveEventType(propName: string, element: Element) {
|
||||||
const eventType = propName
|
const eventType = propName
|
||||||
.replace(/^on/, '')
|
.replace(/^on/, '')
|
||||||
.replace(/Capture$/, '').toLowerCase();
|
.replace(/Capture$/, '').toLowerCase();
|
||||||
@ -54,7 +54,7 @@ function resolveEventType(propName: string, element: HTMLElement) {
|
|||||||
return eventType;
|
return eventType;
|
||||||
}
|
}
|
||||||
|
|
||||||
function canUseEventDelegation(realEventType: string, element: HTMLElement, asCapture: boolean) {
|
function canUseEventDelegation(realEventType: string, element: Element, asCapture: boolean) {
|
||||||
return (
|
return (
|
||||||
!asCapture
|
!asCapture
|
||||||
&& !NON_BUBBLEABLE_EVENTS.has(realEventType)
|
&& !NON_BUBBLEABLE_EVENTS.has(realEventType)
|
||||||
@ -63,7 +63,7 @@ function canUseEventDelegation(realEventType: string, element: HTMLElement, asCa
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addDelegatedListener(eventType: string, element: HTMLElement, handler: Handler) {
|
function addDelegatedListener(eventType: string, element: Element, handler: Handler) {
|
||||||
if (!documentEventCounters[eventType]) {
|
if (!documentEventCounters[eventType]) {
|
||||||
documentEventCounters[eventType] = 0;
|
documentEventCounters[eventType] = 0;
|
||||||
document.addEventListener(eventType, handleEvent);
|
document.addEventListener(eventType, handleEvent);
|
||||||
@ -74,7 +74,7 @@ function addDelegatedListener(eventType: string, element: HTMLElement, handler:
|
|||||||
documentEventCounters[eventType]++;
|
documentEventCounters[eventType]++;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeDelegatedListener(eventType: string, element: HTMLElement) {
|
function removeDelegatedListener(eventType: string, element: Element) {
|
||||||
documentEventCounters[eventType]--;
|
documentEventCounters[eventType]--;
|
||||||
if (!documentEventCounters[eventType]) {
|
if (!documentEventCounters[eventType]) {
|
||||||
// Synchronous deletion on 0 will cause perf degradation in the case of 1 element
|
// Synchronous deletion on 0 will cause perf degradation in the case of 1 element
|
||||||
@ -86,7 +86,7 @@ function removeDelegatedListener(eventType: string, element: HTMLElement) {
|
|||||||
delegatedEventTypesByElement.get(element)!.delete(eventType);
|
delegatedEventTypesByElement.get(element)!.delete(eventType);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeAllDelegatedListeners(element: HTMLElement) {
|
export function removeAllDelegatedListeners(element: Element) {
|
||||||
const eventTypes = delegatedEventTypesByElement.get(element);
|
const eventTypes = delegatedEventTypesByElement.get(element);
|
||||||
if (!eventTypes) {
|
if (!eventTypes) {
|
||||||
return;
|
return;
|
||||||
@ -101,7 +101,7 @@ function handleEvent(realEvent: Event) {
|
|||||||
|
|
||||||
if (events) {
|
if (events) {
|
||||||
let furtherCallsPrevented = false;
|
let furtherCallsPrevented = false;
|
||||||
let current: HTMLElement = realEvent.target as HTMLElement;
|
let current: Element = realEvent.target as Element;
|
||||||
|
|
||||||
const stopPropagation = () => {
|
const stopPropagation = () => {
|
||||||
furtherCallsPrevented = true;
|
furtherCallsPrevented = true;
|
||||||
@ -138,7 +138,7 @@ function handleEvent(realEvent: Event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
current = current.parentNode as HTMLElement;
|
current = current.parentNode as Element;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,7 +151,7 @@ function resolveDelegationRegistry(eventType: string) {
|
|||||||
return delegationRegistryByEventType[eventType];
|
return delegationRegistryByEventType[eventType];
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveDelegatedEventTypes(element: HTMLElement) {
|
function resolveDelegatedEventTypes(element: Element) {
|
||||||
const existing = delegatedEventTypesByElement.get(element);
|
const existing = delegatedEventTypesByElement.get(element);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
return existing;
|
return existing;
|
||||||
|
|||||||
@ -32,6 +32,8 @@ interface SelectionState {
|
|||||||
isCaretAtEnd: boolean;
|
isCaretAtEnd: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DOMElement = HTMLElement | SVGElement;
|
||||||
|
|
||||||
const FILTERED_ATTRIBUTES = new Set(['key', 'ref', 'teactFastList', 'teactOrderKey']);
|
const FILTERED_ATTRIBUTES = new Set(['key', 'ref', 'teactFastList', 'teactOrderKey']);
|
||||||
const HTML_ATTRIBUTES = new Set(['dir', 'role', 'form']);
|
const HTML_ATTRIBUTES = new Set(['dir', 'role', 'form']);
|
||||||
const CONTROLLABLE_TAGS = ['INPUT', 'TEXTAREA', 'SELECT'];
|
const CONTROLLABLE_TAGS = ['INPUT', 'TEXTAREA', 'SELECT'];
|
||||||
@ -71,7 +73,7 @@ function render($element: VirtualElement | undefined, parentEl: HTMLElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderWithVirtual<T extends VirtualElement | undefined>(
|
function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||||
parentEl: HTMLElement,
|
parentEl: DOMElement,
|
||||||
$current: VirtualElement | undefined,
|
$current: VirtualElement | undefined,
|
||||||
$new: T,
|
$new: T,
|
||||||
$parent: VirtualElementParent | VirtualDomHead,
|
$parent: VirtualElementParent | VirtualDomHead,
|
||||||
@ -81,17 +83,22 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
|||||||
nextSibling?: ChildNode;
|
nextSibling?: ChildNode;
|
||||||
forceMoveToEnd?: boolean;
|
forceMoveToEnd?: boolean;
|
||||||
fragment?: DocumentFragment;
|
fragment?: DocumentFragment;
|
||||||
|
isSvg?: true;
|
||||||
} = {},
|
} = {},
|
||||||
): T {
|
): T {
|
||||||
const { skipComponentUpdate, fragment } = options;
|
const { skipComponentUpdate, fragment } = options;
|
||||||
let { nextSibling } = options;
|
let { nextSibling, isSvg } = options;
|
||||||
|
|
||||||
const isCurrentComponent = $current && $current.type === VirtualType.Component;
|
const isCurrentComponent = $current?.type === VirtualType.Component;
|
||||||
const isNewComponent = $new && $new.type === VirtualType.Component;
|
const isNewComponent = $new?.type === VirtualType.Component;
|
||||||
const $newAsReal = $new as VirtualElementReal;
|
const $newAsReal = $new as VirtualElementReal;
|
||||||
|
|
||||||
const isCurrentFragment = $current && !isCurrentComponent && $current.type === VirtualType.Fragment;
|
const isCurrentFragment = !isCurrentComponent && $current?.type === VirtualType.Fragment;
|
||||||
const isNewFragment = $new && !isNewComponent && $new.type === VirtualType.Fragment;
|
const isNewFragment = !isNewComponent && $new?.type === VirtualType.Fragment;
|
||||||
|
|
||||||
|
if ($new?.type === VirtualType.Tag && $new.tag === 'svg') {
|
||||||
|
isSvg = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!skipComponentUpdate
|
!skipComponentUpdate
|
||||||
@ -127,7 +134,9 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
|||||||
$new = initComponent(parentEl, $new as VirtualElementComponent, $parent, index) as unknown as typeof $new;
|
$new = initComponent(parentEl, $new as VirtualElementComponent, $parent, index) as unknown as typeof $new;
|
||||||
}
|
}
|
||||||
|
|
||||||
mountChildren(parentEl, $new as VirtualElementComponent | VirtualElementFragment, { nextSibling, fragment });
|
mountChildren(parentEl, $new as VirtualElementComponent | VirtualElementFragment, {
|
||||||
|
nextSibling, fragment, isSvg,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const canSetTextContent = !fragment
|
const canSetTextContent = !fragment
|
||||||
&& !nextSibling
|
&& !nextSibling
|
||||||
@ -139,12 +148,12 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
|||||||
parentEl.textContent = $newAsReal.value;
|
parentEl.textContent = $newAsReal.value;
|
||||||
$newAsReal.target = parentEl.firstChild!;
|
$newAsReal.target = parentEl.firstChild!;
|
||||||
} else {
|
} else {
|
||||||
const node = createNode($newAsReal);
|
const node = createNode($newAsReal, isSvg);
|
||||||
$newAsReal.target = node;
|
$newAsReal.target = node;
|
||||||
insertBefore(fragment || parentEl, node, nextSibling);
|
insertBefore(fragment || parentEl, node, nextSibling);
|
||||||
|
|
||||||
if ($newAsReal.type === VirtualType.Tag) {
|
if ($newAsReal.type === VirtualType.Tag) {
|
||||||
setElementRef($newAsReal, node as HTMLElement);
|
setElementRef($newAsReal, node as DOMElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,14 +171,16 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
remount(parentEl, $current, undefined);
|
remount(parentEl, $current, undefined);
|
||||||
mountChildren(parentEl, $new as VirtualElementComponent | VirtualElementFragment, { nextSibling, fragment });
|
mountChildren(parentEl, $new as VirtualElementComponent | VirtualElementFragment, {
|
||||||
|
nextSibling, fragment, isSvg,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const node = createNode($newAsReal);
|
const node = createNode($newAsReal, isSvg);
|
||||||
$newAsReal.target = node;
|
$newAsReal.target = node;
|
||||||
remount(parentEl, $current, node, nextSibling);
|
remount(parentEl, $current, node, nextSibling);
|
||||||
|
|
||||||
if ($newAsReal.type === VirtualType.Tag) {
|
if ($newAsReal.type === VirtualType.Tag) {
|
||||||
setElementRef($newAsReal, node as HTMLElement);
|
setElementRef($newAsReal, node as DOMElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -196,14 +207,14 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
|||||||
const $newAsTag = $new as VirtualElementTag;
|
const $newAsTag = $new as VirtualElementTag;
|
||||||
|
|
||||||
setElementRef($current, undefined);
|
setElementRef($current, undefined);
|
||||||
setElementRef($newAsTag, currentTarget as HTMLElement);
|
setElementRef($newAsTag, currentTarget as DOMElement);
|
||||||
|
|
||||||
if (nextSibling || options.forceMoveToEnd) {
|
if (nextSibling || options.forceMoveToEnd) {
|
||||||
insertBefore(parentEl, currentTarget, nextSibling);
|
insertBefore(parentEl, currentTarget, nextSibling);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAttributes($current, $newAsTag, currentTarget as HTMLElement);
|
updateAttributes($current, $newAsTag, currentTarget as DOMElement, isSvg);
|
||||||
renderChildren($current, $newAsTag, currentTarget as HTMLElement);
|
renderChildren($current, $newAsTag, currentTarget as DOMElement, undefined, undefined, isSvg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,7 +224,7 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initComponent(
|
function initComponent(
|
||||||
parentEl: HTMLElement,
|
parentEl: DOMElement,
|
||||||
$element: VirtualElementComponent,
|
$element: VirtualElementComponent,
|
||||||
$parent: VirtualElementParent | VirtualDomHead,
|
$parent: VirtualElementParent | VirtualDomHead,
|
||||||
index: number,
|
index: number,
|
||||||
@ -235,7 +246,7 @@ function updateComponent($current: VirtualElementComponent, $new: VirtualElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setupComponentUpdateListener(
|
function setupComponentUpdateListener(
|
||||||
parentEl: HTMLElement,
|
parentEl: DOMElement,
|
||||||
$element: VirtualElementComponent,
|
$element: VirtualElementComponent,
|
||||||
$parent: VirtualElementParent | VirtualDomHead,
|
$parent: VirtualElementParent | VirtualDomHead,
|
||||||
index: number,
|
index: number,
|
||||||
@ -255,11 +266,12 @@ function setupComponentUpdateListener(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mountChildren(
|
function mountChildren(
|
||||||
parentEl: HTMLElement,
|
parentEl: DOMElement,
|
||||||
$element: VirtualElementComponent | VirtualElementFragment,
|
$element: VirtualElementComponent | VirtualElementFragment,
|
||||||
options: {
|
options: {
|
||||||
nextSibling?: ChildNode;
|
nextSibling?: ChildNode;
|
||||||
fragment?: DocumentFragment;
|
fragment?: DocumentFragment;
|
||||||
|
isSvg?: true;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const { children } = $element;
|
const { children } = $element;
|
||||||
@ -272,13 +284,13 @@ function mountChildren(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function unmountChildren(parentEl: HTMLElement, $element: VirtualElementComponent | VirtualElementFragment) {
|
function unmountChildren(parentEl: DOMElement, $element: VirtualElementComponent | VirtualElementFragment) {
|
||||||
for (const $child of $element.children) {
|
for (const $child of $element.children) {
|
||||||
renderWithVirtual(parentEl, $child, undefined, $element, -1);
|
renderWithVirtual(parentEl, $child, undefined, $element, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNode($element: VirtualElementReal): Node {
|
function createNode($element: VirtualElementReal, isSvg?: true): Node {
|
||||||
if ($element.type === VirtualType.Empty) {
|
if ($element.type === VirtualType.Empty) {
|
||||||
return document.createTextNode('');
|
return document.createTextNode('');
|
||||||
}
|
}
|
||||||
@ -288,7 +300,7 @@ function createNode($element: VirtualElementReal): Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { tag, props, children } = $element;
|
const { tag, props, children } = $element;
|
||||||
const element = document.createElement(tag);
|
const element = isSvg ? document.createElementNS('http://www.w3.org/2000/svg', tag) : document.createElement(tag);
|
||||||
|
|
||||||
processControlled(tag, props);
|
processControlled(tag, props);
|
||||||
|
|
||||||
@ -297,7 +309,7 @@ function createNode($element: VirtualElementReal): Node {
|
|||||||
if (!props.hasOwnProperty(key)) continue;
|
if (!props.hasOwnProperty(key)) continue;
|
||||||
|
|
||||||
if (props[key] !== undefined) {
|
if (props[key] !== undefined) {
|
||||||
setAttribute(element, key, props[key]);
|
setAttribute(element, key, props[key], isSvg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,7 +317,7 @@ function createNode($element: VirtualElementReal): Node {
|
|||||||
|
|
||||||
for (let i = 0, l = children.length; i < l; i++) {
|
for (let i = 0, l = children.length; i < l; i++) {
|
||||||
const $child = children[i];
|
const $child = children[i];
|
||||||
const $renderedChild = renderWithVirtual(element, undefined, $child, $element, i);
|
const $renderedChild = renderWithVirtual(element, undefined, $child, $element, i, { isSvg });
|
||||||
if ($renderedChild !== $child) {
|
if ($renderedChild !== $child) {
|
||||||
children[i] = $renderedChild;
|
children[i] = $renderedChild;
|
||||||
}
|
}
|
||||||
@ -315,7 +327,7 @@ function createNode($element: VirtualElementReal): Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function remount(
|
function remount(
|
||||||
parentEl: HTMLElement,
|
parentEl: DOMElement,
|
||||||
$current: VirtualElement,
|
$current: VirtualElement,
|
||||||
node: Node | undefined,
|
node: Node | undefined,
|
||||||
componentNextSibling?: ChildNode,
|
componentNextSibling?: ChildNode,
|
||||||
@ -366,7 +378,7 @@ function unmountRealTree($element: VirtualElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertBefore(parentEl: HTMLElement | DocumentFragment, node: Node, nextSibling?: ChildNode) {
|
function insertBefore(parentEl: DOMElement | DocumentFragment, node: Node, nextSibling?: ChildNode) {
|
||||||
if (nextSibling) {
|
if (nextSibling) {
|
||||||
parentEl.insertBefore(node, nextSibling);
|
parentEl.insertBefore(node, nextSibling);
|
||||||
} else {
|
} else {
|
||||||
@ -386,9 +398,10 @@ function getNextSibling($current: VirtualElement): ChildNode | undefined {
|
|||||||
function renderChildren(
|
function renderChildren(
|
||||||
$current: VirtualElementParent,
|
$current: VirtualElementParent,
|
||||||
$new: VirtualElementParent,
|
$new: VirtualElementParent,
|
||||||
currentEl: HTMLElement,
|
currentEl: DOMElement,
|
||||||
nextSibling?: ChildNode,
|
nextSibling?: ChildNode,
|
||||||
forceMoveToEnd = false,
|
forceMoveToEnd = false,
|
||||||
|
isSvg?: true,
|
||||||
) {
|
) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
DEBUG_checkKeyUniqueness($new.children);
|
DEBUG_checkKeyUniqueness($new.children);
|
||||||
@ -419,7 +432,7 @@ function renderChildren(
|
|||||||
newChildren[i],
|
newChildren[i],
|
||||||
$new,
|
$new,
|
||||||
i,
|
i,
|
||||||
i >= currentChildrenLength ? { fragment } : { nextSibling, forceMoveToEnd },
|
i >= currentChildrenLength ? { fragment, isSvg } : { nextSibling, forceMoveToEnd, isSvg },
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($renderedChild && $renderedChild !== newChildren[i]) {
|
if ($renderedChild && $renderedChild !== newChildren[i]) {
|
||||||
@ -434,7 +447,7 @@ function renderChildren(
|
|||||||
|
|
||||||
// This function allows to prepend/append a bunch of new DOM nodes to the top/bottom of preserved ones.
|
// This function allows to prepend/append a bunch of new DOM nodes to the top/bottom of preserved ones.
|
||||||
// It also allows to selectively move particular preserved nodes within their DOM list.
|
// It also allows to selectively move particular preserved nodes within their DOM list.
|
||||||
function renderFastListChildren($current: VirtualElementParent, $new: VirtualElementParent, currentEl: HTMLElement) {
|
function renderFastListChildren($current: VirtualElementParent, $new: VirtualElementParent, currentEl: DOMElement) {
|
||||||
const currentChildren = $current.children;
|
const currentChildren = $current.children;
|
||||||
const newChildren = $new.children;
|
const newChildren = $new.children;
|
||||||
|
|
||||||
@ -549,7 +562,7 @@ function renderFastListChildren($current: VirtualElementParent, $new: VirtualEle
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderFragment(
|
function renderFragment(
|
||||||
fragmentIndex: number, fragmentSize: number, parentEl: HTMLElement, $parent: VirtualElementParent,
|
fragmentIndex: number, fragmentSize: number, parentEl: DOMElement, $parent: VirtualElementParent,
|
||||||
) {
|
) {
|
||||||
const nextSibling = parentEl.childNodes[fragmentIndex];
|
const nextSibling = parentEl.childNodes[fragmentIndex];
|
||||||
|
|
||||||
@ -576,13 +589,13 @@ function renderFragment(
|
|||||||
insertBefore(parentEl, fragment, nextSibling);
|
insertBefore(parentEl, fragment, nextSibling);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setElementRef($element: VirtualElementTag, htmlElement: HTMLElement | undefined) {
|
function setElementRef($element: VirtualElementTag, DOMElement: DOMElement | undefined) {
|
||||||
const { ref } = $element.props;
|
const { ref } = $element.props;
|
||||||
|
|
||||||
if (typeof ref === 'object') {
|
if (typeof ref === 'object') {
|
||||||
ref.current = htmlElement;
|
ref.current = DOMElement;
|
||||||
} else if (typeof ref === 'function') {
|
} else if (typeof ref === 'function') {
|
||||||
ref(htmlElement);
|
ref(DOMElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -629,7 +642,7 @@ function processControlled(tag: string, props: AnyLiteral) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function processUncontrolledOnMount(element: HTMLElement, props: AnyLiteral) {
|
function processUncontrolledOnMount(element: DOMElement, props: AnyLiteral) {
|
||||||
if (!CONTROLLABLE_TAGS.includes(element.tagName)) {
|
if (!CONTROLLABLE_TAGS.includes(element.tagName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -643,7 +656,7 @@ function processUncontrolledOnMount(element: HTMLElement, props: AnyLiteral) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateAttributes($current: VirtualElementTag, $new: VirtualElementTag, element: HTMLElement) {
|
function updateAttributes($current: VirtualElementTag, $new: VirtualElementTag, element: DOMElement, isSvg?: true) {
|
||||||
processControlled(element.tagName, $new.props);
|
processControlled(element.tagName, $new.props);
|
||||||
|
|
||||||
const currentEntries = Object.entries($current.props);
|
const currentEntries = Object.entries($current.props);
|
||||||
@ -667,14 +680,14 @@ function updateAttributes($current: VirtualElementTag, $new: VirtualElementTag,
|
|||||||
const currentValue = $current.props[key];
|
const currentValue = $current.props[key];
|
||||||
|
|
||||||
if (newValue !== undefined && newValue !== currentValue) {
|
if (newValue !== undefined && newValue !== currentValue) {
|
||||||
setAttribute(element, key, newValue);
|
setAttribute(element, key, newValue, isSvg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setAttribute(element: HTMLElement, key: string, value: any) {
|
function setAttribute(element: DOMElement, key: string, value: any, isSvg?: true) {
|
||||||
if (key === 'className') {
|
if (key === 'className') {
|
||||||
updateClassName(element, value);
|
updateClassName(element, value, isSvg);
|
||||||
} else if (key === 'value') {
|
} else if (key === 'value') {
|
||||||
const inputEl = element as HTMLInputElement;
|
const inputEl = element as HTMLInputElement;
|
||||||
|
|
||||||
@ -701,14 +714,14 @@ function setAttribute(element: HTMLElement, key: string, value: any) {
|
|||||||
element.innerHTML = value.__html;
|
element.innerHTML = value.__html;
|
||||||
} else if (key.startsWith('on')) {
|
} else if (key.startsWith('on')) {
|
||||||
addEventListener(element, key, value, key.endsWith('Capture'));
|
addEventListener(element, key, value, key.endsWith('Capture'));
|
||||||
} else if (key.startsWith('data-') || key.startsWith('aria-') || HTML_ATTRIBUTES.has(key)) {
|
} else if (isSvg || key.startsWith('data-') || key.startsWith('aria-') || HTML_ATTRIBUTES.has(key)) {
|
||||||
element.setAttribute(key, value);
|
element.setAttribute(key, value);
|
||||||
} else if (!FILTERED_ATTRIBUTES.has(key)) {
|
} else if (!FILTERED_ATTRIBUTES.has(key)) {
|
||||||
(element as any)[MAPPED_ATTRIBUTES[key] || key] = value;
|
(element as any)[MAPPED_ATTRIBUTES[key] || key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeAttribute(element: HTMLElement, key: string, value: any) {
|
function removeAttribute(element: DOMElement, key: string, value: any) {
|
||||||
if (key === 'className') {
|
if (key === 'className') {
|
||||||
updateClassName(element, '');
|
updateClassName(element, '');
|
||||||
} else if (key === 'value') {
|
} else if (key === 'value') {
|
||||||
@ -724,10 +737,16 @@ function removeAttribute(element: HTMLElement, key: string, value: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateClassName(element: HTMLElement, value: string) {
|
function updateClassName(element: DOMElement, value: string, isSvg?: true) {
|
||||||
|
if (isSvg) {
|
||||||
|
element.setAttribute('class', value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const htmlElement = element as HTMLElement;
|
||||||
const extra = extraClasses.get(element);
|
const extra = extraClasses.get(element);
|
||||||
if (!extra) {
|
if (!extra) {
|
||||||
element.className = value;
|
htmlElement.className = value;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -736,10 +755,10 @@ function updateClassName(element: HTMLElement, value: string) {
|
|||||||
extraArray.push(value);
|
extraArray.push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
element.className = extraArray.join(' ');
|
htmlElement.className = extraArray.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStyle(element: HTMLElement, value: string) {
|
function updateStyle(element: DOMElement, value: string) {
|
||||||
element.style.cssText = value;
|
element.style.cssText = value;
|
||||||
|
|
||||||
const extraObject = extraStyles.get(element);
|
const extraObject = extraStyles.get(element);
|
||||||
@ -748,7 +767,7 @@ function updateStyle(element: HTMLElement, value: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addExtraClass(element: Element, className: string, forceSingle = false) {
|
export function addExtraClass(element: DOMElement, className: string, forceSingle = false) {
|
||||||
if (!forceSingle) {
|
if (!forceSingle) {
|
||||||
const classNames = className.split(' ');
|
const classNames = className.split(' ');
|
||||||
if (classNames.length > 1) {
|
if (classNames.length > 1) {
|
||||||
@ -770,7 +789,7 @@ export function addExtraClass(element: Element, className: string, forceSingle =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeExtraClass(element: Element, className: string, forceSingle = false) {
|
export function removeExtraClass(element: DOMElement, className: string, forceSingle = false) {
|
||||||
if (!forceSingle) {
|
if (!forceSingle) {
|
||||||
const classNames = className.split(' ');
|
const classNames = className.split(' ');
|
||||||
if (classNames.length > 1) {
|
if (classNames.length > 1) {
|
||||||
@ -794,7 +813,7 @@ export function removeExtraClass(element: Element, className: string, forceSingl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toggleExtraClass(element: Element, className: string, force?: boolean, forceSingle = false) {
|
export function toggleExtraClass(element: DOMElement, className: string, force?: boolean, forceSingle = false) {
|
||||||
if (!forceSingle) {
|
if (!forceSingle) {
|
||||||
const classNames = className.split(' ');
|
const classNames = className.split(' ');
|
||||||
if (classNames.length > 1) {
|
if (classNames.length > 1) {
|
||||||
@ -815,12 +834,12 @@ export function toggleExtraClass(element: Element, className: string, force?: bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setExtraStyles(element: HTMLElement, styles: Partial<CSSStyleDeclaration> & AnyLiteral) {
|
export function setExtraStyles(element: DOMElement, styles: Partial<CSSStyleDeclaration> & AnyLiteral) {
|
||||||
extraStyles.set(element, styles);
|
extraStyles.set(element, styles);
|
||||||
applyExtraStyles(element);
|
applyExtraStyles(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyExtraStyles(element: HTMLElement) {
|
function applyExtraStyles(element: DOMElement) {
|
||||||
const standardStyles = Object.entries(extraStyles.get(element)!).reduce<Record<string, string>>(
|
const standardStyles = Object.entries(extraStyles.get(element)!).reduce<Record<string, string>>(
|
||||||
(acc, [prop, value]) => {
|
(acc, [prop, value]) => {
|
||||||
if (prop.startsWith('--')) {
|
if (prop.startsWith('--')) {
|
||||||
|
|||||||
@ -37,7 +37,7 @@ interface VirtualElementText {
|
|||||||
|
|
||||||
export interface VirtualElementTag {
|
export interface VirtualElementTag {
|
||||||
type: VirtualType.Tag;
|
type: VirtualType.Tag;
|
||||||
target?: HTMLElement;
|
target?: HTMLElement | SVGElement;
|
||||||
tag: string;
|
tag: string;
|
||||||
props: Props;
|
props: Props;
|
||||||
children: VirtualElementChildren;
|
children: VirtualElementChildren;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user