main
js 644 lines 14.9 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities';
9
10 import Transform from 'art/core/transform';
11 import Mode from 'art/modes/current';
12
13 import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
14
15 import {
16 DefaultEventPriority,
17 NoEventPriority,
18 } from 'react-reconciler/src/ReactEventPriorities';
19 import type {ReactContext} from 'shared/ReactTypes';
20 import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
21
22 export {default as rendererVersion} from 'shared/ReactVersion';
23 export const rendererPackageName = 'react-art';
24 export const extraDevToolsConfig = null;
25
26 const pooledTransform = new Transform();
27
28 const NO_CONTEXT = {};
29 if (__DEV__) {
30 Object.freeze(NO_CONTEXT);
31 }
32
33 export type TransitionStatus = mixed;
34
35 /** Helper Methods */
36
37 function addEventListeners(instance, type, listener) {
38 // We need to explicitly unregister before unmount.
39 // For this reason we need to track subscriptions.
40 if (!instance._listeners) {
41 instance._listeners = {};
42 instance._subscriptions = {};
43 }
44
45 instance._listeners[type] = listener;
46
47 if (listener) {
48 if (!instance._subscriptions[type]) {
49 instance._subscriptions[type] = instance.subscribe(
50 type,
51 createEventHandler(instance),
52 instance,
53 );
54 }
55 } else {
56 if (instance._subscriptions[type]) {
57 instance._subscriptions[type]();
58 delete instance._subscriptions[type];
59 }
60 }
61 }
62
63 function createEventHandler(instance) {
64 return function handleEvent(event) {
65 const listener = instance._listeners[event.type];
66
67 if (!listener) {
68 // Noop
69 } else if (typeof listener === 'function') {
70 listener.call(instance, event);
71 } else if (listener.handleEvent) {
72 listener.handleEvent(event);
73 }
74 };
75 }
76
77 function destroyEventListeners(instance) {
78 if (instance._subscriptions) {
79 for (const type in instance._subscriptions) {
80 instance._subscriptions[type]();
81 }
82 }
83
84 instance._subscriptions = null;
85 instance._listeners = null;
86 }
87
88 function getScaleX(props) {
89 if (props.scaleX != null) {
90 return props.scaleX;
91 } else if (props.scale != null) {
92 return props.scale;
93 } else {
94 return 1;
95 }
96 }
97
98 function getScaleY(props) {
99 if (props.scaleY != null) {
100 return props.scaleY;
101 } else if (props.scale != null) {
102 return props.scale;
103 } else {
104 return 1;
105 }
106 }
107
108 function isSameFont(oldFont, newFont) {
109 if (oldFont === newFont) {
110 return true;
111 } else if (typeof newFont === 'string' || typeof oldFont === 'string') {
112 return false;
113 } else {
114 return (
115 newFont.fontSize === oldFont.fontSize &&
116 newFont.fontStyle === oldFont.fontStyle &&
117 newFont.fontVariant === oldFont.fontVariant &&
118 newFont.fontWeight === oldFont.fontWeight &&
119 newFont.fontFamily === oldFont.fontFamily
120 );
121 }
122 }
123
124 /** Render Methods */
125
126 function applyClippingRectangleProps(instance, props, prevProps = {}) {
127 applyNodeProps(instance, props, prevProps);
128
129 instance.width = props.width;
130 instance.height = props.height;
131 }
132
133 function applyGroupProps(instance, props, prevProps = {}) {
134 applyNodeProps(instance, props, prevProps);
135
136 instance.width = props.width;
137 instance.height = props.height;
138 }
139
140 function applyNodeProps(instance, props, prevProps = {}) {
141 const scaleX = getScaleX(props);
142 const scaleY = getScaleY(props);
143
144 pooledTransform
145 .transformTo(1, 0, 0, 1, 0, 0)
146 .move(props.x || 0, props.y || 0)
147 .rotate(props.rotation || 0, props.originX, props.originY)
148 .scale(scaleX, scaleY, props.originX, props.originY);
149
150 if (props.transform != null) {
151 pooledTransform.transform(props.transform);
152 }
153
154 if (
155 instance.xx !== pooledTransform.xx ||
156 instance.yx !== pooledTransform.yx ||
157 instance.xy !== pooledTransform.xy ||
158 instance.yy !== pooledTransform.yy ||
159 instance.x !== pooledTransform.x ||
160 instance.y !== pooledTransform.y
161 ) {
162 instance.transformTo(pooledTransform);
163 }
164
165 if (props.cursor !== prevProps.cursor || props.title !== prevProps.title) {
166 instance.indicate(props.cursor, props.title);
167 }
168
169 if (instance.blend && props.opacity !== prevProps.opacity) {
170 instance.blend(props.opacity == null ? 1 : props.opacity);
171 }
172
173 if (props.visible !== prevProps.visible) {
174 if (props.visible == null || props.visible) {
175 instance.show();
176 } else {
177 instance.hide();
178 }
179 }
180
181 for (const type in EVENT_TYPES) {
182 addEventListeners(instance, EVENT_TYPES[type], props[type]);
183 }
184 }
185
186 function applyRenderableNodeProps(instance, props, prevProps = {}) {
187 applyNodeProps(instance, props, prevProps);
188
189 if (prevProps.fill !== props.fill) {
190 if (props.fill && props.fill.applyFill) {
191 props.fill.applyFill(instance);
192 } else {
193 instance.fill(props.fill);
194 }
195 }
196 if (
197 prevProps.stroke !== props.stroke ||
198 prevProps.strokeWidth !== props.strokeWidth ||
199 prevProps.strokeCap !== props.strokeCap ||
200 prevProps.strokeJoin !== props.strokeJoin ||
201 // TODO: Consider deep check of stokeDash; may benefit VML in IE.
202 prevProps.strokeDash !== props.strokeDash
203 ) {
204 instance.stroke(
205 props.stroke,
206 props.strokeWidth,
207 props.strokeCap,
208 props.strokeJoin,
209 props.strokeDash,
210 );
211 }
212 }
213
214 function applyShapeProps(instance, props, prevProps = {}) {
215 applyRenderableNodeProps(instance, props, prevProps);
216
217 const path = props.d || childrenAsString(props.children);
218
219 const prevDelta = instance._prevDelta;
220 const prevPath = instance._prevPath;
221
222 if (
223 path !== prevPath ||
224 path.delta !== prevDelta ||
225 prevProps.height !== props.height ||
226 prevProps.width !== props.width
227 ) {
228 instance.draw(path, props.width, props.height);
229
230 instance._prevDelta = path.delta;
231 instance._prevPath = path;
232 }
233 }
234
235 function applyTextProps(instance, props, prevProps = {}) {
236 applyRenderableNodeProps(instance, props, prevProps);
237
238 const string = props.children;
239
240 if (
241 instance._currentString !== string ||
242 !isSameFont(props.font, prevProps.font) ||
243 props.alignment !== prevProps.alignment ||
244 props.path !== prevProps.path
245 ) {
246 instance.draw(string, props.font, props.alignment, props.path);
247
248 instance._currentString = string;
249 }
250 }
251
252 export * from 'react-reconciler/src/ReactFiberConfigWithNoPersistence';
253 export * from 'react-reconciler/src/ReactFiberConfigWithNoHydration';
254 export * from 'react-reconciler/src/ReactFiberConfigWithNoScopes';
255 export * from 'react-reconciler/src/ReactFiberConfigWithNoTestSelectors';
256 export * from 'react-reconciler/src/ReactFiberConfigWithNoMicrotasks';
257 export * from 'react-reconciler/src/ReactFiberConfigWithNoResources';
258 export * from 'react-reconciler/src/ReactFiberConfigWithNoSingletons';
259
260 export function appendInitialChild(parentInstance, child) {
261 if (typeof child === 'string') {
262 // Noop for string children of Text (eg <Text>{'foo'}{'bar'}</Text>)
263 throw new Error('Text children should already be flattened.');
264 }
265
266 child.inject(parentInstance);
267 }
268
269 export function createInstance(type, props, internalInstanceHandle) {
270 let instance;
271
272 switch (type) {
273 case TYPES.CLIPPING_RECTANGLE:
274 instance = Mode.ClippingRectangle();
275 instance._applyProps = applyClippingRectangleProps;
276 break;
277 case TYPES.GROUP:
278 instance = Mode.Group();
279 instance._applyProps = applyGroupProps;
280 break;
281 case TYPES.SHAPE:
282 instance = Mode.Shape();
283 instance._applyProps = applyShapeProps;
284 break;
285 case TYPES.TEXT:
286 instance = Mode.Text(
287 props.children,
288 props.font,
289 props.alignment,
290 props.path,
291 );
292 instance._applyProps = applyTextProps;
293 break;
294 }
295
296 if (!instance) {
297 throw new Error(`ReactART does not support the type "${type}"`);
298 }
299
300 instance._applyProps(instance, props);
301
302 return instance;
303 }
304
305 export function cloneMutableInstance(instance, keepChildren) {
306 return instance;
307 }
308
309 export function createTextInstance(
310 text,
311 rootContainerInstance,
312 internalInstanceHandle,
313 ) {
314 return text;
315 }
316
317 export function cloneMutableTextInstance(textInstance) {
318 return textInstance;
319 }
320
321 export type FragmentInstanceType = null;
322
323 export function createFragmentInstance(fiber): null {
324 return null;
325 }
326
327 export function updateFragmentInstanceFiber(fiber, instance): void {
328 // Noop
329 }
330
331 export function commitNewChildToFragmentInstance(
332 child,
333 fragmentInstance,
334 ): void {
335 // Noop
336 }
337
338 export function deleteChildFromFragmentInstance(child, fragmentInstance): void {
339 // Noop
340 }
341
342 export function finalizeInitialChildren(domElement, type, props) {
343 return false;
344 }
345
346 export function getPublicInstance(instance) {
347 return instance;
348 }
349
350 export function prepareForCommit() {
351 // Noop
352 return null;
353 }
354
355 export function resetAfterCommit() {
356 // Noop
357 }
358
359 export function resetTextContent(domElement) {
360 // Noop
361 }
362
363 export function getRootHostContext() {
364 return NO_CONTEXT;
365 }
366
367 export function getChildHostContext() {
368 return NO_CONTEXT;
369 }
370
371 export const scheduleTimeout = setTimeout;
372 export const cancelTimeout = clearTimeout;
373 export const noTimeout = -1;
374
375 export function shouldSetTextContent(type, props) {
376 return (
377 typeof props.children === 'string' || typeof props.children === 'number'
378 );
379 }
380
381 let currentUpdatePriority: EventPriority = NoEventPriority;
382
383 export function setCurrentUpdatePriority(newPriority: EventPriority): void {
384 currentUpdatePriority = newPriority;
385 }
386
387 export function getCurrentUpdatePriority(): EventPriority {
388 return currentUpdatePriority;
389 }
390
391 export function resolveUpdatePriority(): EventPriority {
392 return currentUpdatePriority || DefaultEventPriority;
393 }
394
395 export function trackSchedulerEvent(): void {}
396
397 export function resolveEventType(): null | string {
398 return null;
399 }
400
401 export function resolveEventTimeStamp(): number {
402 return -1.1;
403 }
404
405 export function shouldAttemptEagerTransition() {
406 return false;
407 }
408
409 // The ART renderer is secondary to the React DOM renderer.
410 export const isPrimaryRenderer = false;
411
412 // The ART renderer shouldn't trigger missing act() warnings
413 export const warnsIfNotActing = false;
414
415 export const supportsMutation = true;
416
417 export function appendChild(parentInstance, child) {
418 if (child.parentNode === parentInstance) {
419 child.eject();
420 }
421 child.inject(parentInstance);
422 }
423
424 export function appendChildToContainer(parentInstance, child) {
425 if (child.parentNode === parentInstance) {
426 child.eject();
427 }
428 child.inject(parentInstance);
429 }
430
431 export function insertBefore(parentInstance, child, beforeChild) {
432 if (child === beforeChild) {
433 throw new Error('ReactART: Can not insert node before itself');
434 }
435
436 child.injectBefore(beforeChild);
437 }
438
439 export function insertInContainerBefore(parentInstance, child, beforeChild) {
440 if (child === beforeChild) {
441 throw new Error('ReactART: Can not insert node before itself');
442 }
443
444 child.injectBefore(beforeChild);
445 }
446
447 export function removeChild(parentInstance, child) {
448 destroyEventListeners(child);
449 child.eject();
450 }
451
452 export function removeChildFromContainer(parentInstance, child) {
453 destroyEventListeners(child);
454 child.eject();
455 }
456
457 export function commitTextUpdate(textInstance, oldText, newText) {
458 // Noop
459 }
460
461 export function commitMount(instance, type, newProps) {
462 // Noop
463 }
464
465 export function commitUpdate(instance, type, oldProps, newProps) {
466 instance._applyProps(instance, newProps, oldProps);
467 }
468
469 export function hideInstance(instance) {
470 instance.hide();
471 }
472
473 export function hideTextInstance(textInstance) {
474 // Noop
475 }
476
477 export function unhideInstance(instance, props) {
478 if (props.visible == null || props.visible) {
479 instance.show();
480 }
481 }
482
483 export function unhideTextInstance(textInstance, text): void {
484 // Noop
485 }
486
487 export function applyViewTransitionName(instance, name, className) {
488 // Noop
489 }
490
491 export function restoreViewTransitionName(instance, props) {
492 // Noop
493 }
494
495 export function cancelViewTransitionName(instance, name, props) {
496 // Noop
497 }
498
499 export function cancelRootViewTransitionName(rootContainer) {
500 // Noop
501 }
502
503 export function restoreRootViewTransitionName(rootContainer) {
504 // Noop
505 }
506
507 export function cloneRootViewTransitionContainer(rootContainer) {
508 throw new Error('Not implemented.');
509 }
510
511 export function removeRootViewTransitionClone(rootContainer, clone) {
512 throw new Error('Not implemented.');
513 }
514
515 export type InstanceMeasurement = null;
516
517 export function measureInstance(instance) {
518 return null;
519 }
520
521 export function measureClonedInstance(instance) {
522 return null;
523 }
524
525 export function wasInstanceInViewport(measurement): boolean {
526 return true;
527 }
528
529 export function hasInstanceChanged(oldMeasurement, newMeasurement): boolean {
530 return false;
531 }
532
533 export function hasInstanceAffectedParent(
534 oldMeasurement,
535 newMeasurement,
536 ): boolean {
537 return false;
538 }
539
540 export function startViewTransition() {
541 return null;
542 }
543
544 export type RunningViewTransition = null;
545
546 export function startGestureTransition() {
547 return null;
548 }
549
550 export function stopViewTransition(transition: RunningViewTransition) {}
551
552 export function addViewTransitionFinishedListener(
553 transition: RunningViewTransition,
554 callback: () => void,
555 ) {
556 callback();
557 }
558
559 export type ViewTransitionInstance = null | {name: string, ...};
560
561 export function createViewTransitionInstance(
562 name: string,
563 ): ViewTransitionInstance {
564 return null;
565 }
566
567 export type GestureTimeline = null;
568
569 export function getCurrentGestureOffset(provider: GestureTimeline): number {
570 throw new Error('startGestureTransition is not yet supported in react-art.');
571 }
572
573 export function clearContainer(container) {
574 // TODO Implement this
575 }
576
577 export function getInstanceFromNode(node): null {
578 return null;
579 }
580
581 export function beforeActiveInstanceBlur(internalInstanceHandle: Object) {
582 // noop
583 }
584
585 export function afterActiveInstanceBlur() {
586 // noop
587 }
588
589 export function preparePortalMount(portalInstance: any): void {
590 // noop
591 }
592
593 // eslint-disable-next-line no-undef
594 export function detachDeletedInstance(node: Instance): void {
595 // noop
596 }
597
598 export function requestPostPaintCallback(callback: (time: number) => void) {
599 // noop
600 }
601
602 export function maySuspendCommit(type, props) {
603 return false;
604 }
605
606 export function maySuspendCommitOnUpdate(type, oldProps, newProps) {
607 return false;
608 }
609
610 export function maySuspendCommitInSyncRender(type, props) {
611 return false;
612 }
613
614 export function preloadInstance(type, props) {
615 // Return true to indicate it's already loaded
616 return true;
617 }
618
619 export function startSuspendingCommit() {
620 return null;
621 }
622
623 export function suspendInstance(state, instance, type, props) {}
624
625 export function suspendOnActiveViewTransition(state, container) {}
626
627 export function waitForCommitToBeReady(timeoutOffset) {
628 return null;
629 }
630
631 export function getSuspendedCommitReason(state, rootContainer) {
632 return null;
633 }
634
635 export const NotPendingTransition = null;
636 export const HostTransitionContext: ReactContext<TransitionStatus> = {
637 $$typeof: REACT_CONTEXT_TYPE,
638 Provider: (null: any),
639 Consumer: (null: any),
640 _currentValue: NotPendingTransition,
641 _currentValue2: NotPendingTransition,
642 _threadCount: 0,
643 };
644 export function resetFormInstance() {}