diff --git a/pages/native-refs/default.page.tsx b/pages/native-refs/default.page.tsx new file mode 100644 index 0000000000..e86c6da473 --- /dev/null +++ b/pages/native-refs/default.page.tsx @@ -0,0 +1,225 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; + +import Box from '~components/box'; +import Button from '~components/button'; +import ColumnLayout from '~components/column-layout'; +import Container from '~components/container'; +import Header from '~components/header'; +import KeyValuePairs from '~components/key-value-pairs'; +import SpaceBetween from '~components/space-between'; +import StatusIndicator, { StatusIndicatorProps } from '~components/status-indicator'; +import Textarea, { TextareaProps } from '~components/textarea'; + +import { SimplePage } from '../app/templates'; + +const INSERTED = `[inserted]`; + +function getVisibility(ratio: number | null): { type: StatusIndicatorProps.Type; label: string } { + return ratio === null + ? { type: `pending`, label: `Not measured` } + : ratio === 0 + ? { type: `error`, label: `Not visible` } + : ratio < 1 + ? { type: `warning`, label: `Partially visible` } + : { type: `success`, label: `Fully visible` }; +} + +export default function NativeRefsPage() { + const componentRef = useRef(null); + const nativeRef = useRef(null); + const containerRef = useRef(null); + + const [value, setValue] = useState(`Select or place the cursor in this text. Drag the corner to resize.`); + const [nativeEdit, setNativeEdit] = useState<{ + readonly caret: number; + readonly inputType: string; + readonly data: string | null; + } | null>(null); + const [size, setSize] = useState<{ width: number; height: number } | null>(null); + const [visibleRatio, setVisibleRatio] = useState(null); + const [pendingCursor, setPendingCursor] = useState(null); + + useLayoutEffect(() => { + nativeRef.current?.scrollIntoView({ behavior: `smooth`, block: `center` }); + }, []); + + useEffect(() => { + const container = containerRef.current; + const element = nativeRef.current; + + if (container && element) { + const observer = new IntersectionObserver( + entries => { + setVisibleRatio(entries[0].intersectionRatio); + }, + { root: container, threshold: [0, 0.25, 0.5, 0.75, 1] } + ); + observer.observe(element); + + return () => { + observer.disconnect(); + }; + } + }, []); + + useEffect(() => { + const element = nativeRef.current; + + if (element) { + const observer = new ResizeObserver(entries => { + const { width, height } = entries[0].contentRect; + + setSize({ width: Math.round(width), height: Math.round(height) }); + }); + + observer.observe(element); + + return () => { + observer.disconnect(); + }; + } + }, []); + + useLayoutEffect(() => { + if (pendingCursor !== null) { + nativeRef.current?.setSelectionRange(pendingCursor, pendingCursor); + nativeRef.current?.focus(); + + setPendingCursor(null); + } + }, [pendingCursor]); + + const handleChange: TextareaProps['onChange'] = event => { + setValue(event.detail.value); + }; + + const handleNativeChange = (event: React.ChangeEvent): void => { + const inputEvent = event.nativeEvent as InputEvent; + setNativeEdit({ + caret: event.currentTarget.selectionStart, + inputType: inputEvent.inputType ?? `unknown`, + data: inputEvent.data, + }); + }; + + const focusViaComponentRef = (): void => { + componentRef.current?.focus(); + }; + + const focusViaNativeRef = (): void => { + nativeRef.current?.focus(); + }; + + const insertViaNativeRef = (): void => { + const element = nativeRef.current; + if (element) { + const start = element.selectionStart ?? 0; + const end = element.selectionEnd ?? 0; + + setValue(value.slice(0, start) + INSERTED + value.slice(end)); + setPendingCursor(start + INSERTED.length); + } + }; + + const scrollViaNativeRef = (): void => { + nativeRef.current?.scrollIntoView({ behavior: `smooth`, block: `center` }); + }; + + const visibility = getVisibility(visibleRatio); + const ratioLabel = visibleRatio === null ? `-` : `${Math.round(visibleRatio * 100)}%`; + + return ( + + +
+ +
+
+