Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | 1x 1x 1x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x | import * as React from 'react';
import {bind} from '@jsonjoy.com/collaborative-input';
import type {CollaborativeStr} from '@jsonjoy.com/collaborative-str';
/**
* Imperative handle that a custom input element, rendered through the
* {@link CollaborativeInputProps.inp} render prop, may expose to
* `<CollaborativeInput>` via its `ref`.
*/
export interface CollaborativeInputHandle {
/** The underlying `<input>` or `<textarea>` element to bind the CRDT to. */
readonly input: HTMLInputElement | HTMLTextAreaElement | null;
/**
* Optional callback to re-measure/resize the element. Called once after the
* initial binding and whenever the bound "str" node changes (i.e. on remote
* or programmatic edits that don't emit a DOM `input` event).
*/
resize?: () => void;
}
/** Props passed to the {@link CollaborativeInputProps.inp} render prop. */
export interface CollaborativeInputRenderProps {
/**
* Ref which must receive the input element's {@link CollaborativeInputHandle}.
* Typed loosely so it can be spread onto any element/component whose handle is
* structurally compatible with {@link CollaborativeInputHandle} (such as
* `<FlexibleInput>`, whose handle is a superset).
*/
ref: React.Ref<any>;
/** Whether the input is multiline. */
multiline?: boolean;
/** The value is driven by the CRDT binding, not controlled by React. */
uncontrolled: true;
}
export interface CollaborativeInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
/** JSON CRDT "str" node API. */
str: () => CollaborativeStr;
/**
* Whether to poll for updates the underlying <input> or <textarea> element
* in case some third-party code modifies the value of the input element.
*/
polling?: boolean;
/** Whether the input is multiline. */
multiline?: boolean;
/**
* Render prop for a custom input element. It receives props that must be
* spread onto a component which exposes a {@link CollaborativeInputHandle}
* through its `ref` (such as `<FlexibleInput>`):
*
* ```tsx
* <CollaborativeInput str={str} inp={(props) => <FlexibleInput {...props} />} />
* ```
*
* When omitted, a plain `<input>`/`<textarea>` element is rendered and all
* other props are forwarded to it.
*/
inp?: (props: CollaborativeInputRenderProps) => React.ReactElement;
}
export const CollaborativeInput: React.FC<CollaborativeInputProps> = ({inp, str, polling, multiline, ...rest}) => {
const handleRef = React.useRef<CollaborativeInputHandle | null>(null);
const domRef = React.useRef<HTMLInputElement | HTMLTextAreaElement | null>(null);
// biome-ignore lint/correctness/useExhaustiveDependencies: the binding only depends on the "str" node and polling
React.useLayoutEffect(() => {
const input = inp ? (handleRef.current?.input ?? null) : domRef.current;
Iif (!input) return;
const unbind = bind(str, input, !!polling);
let stop: undefined | (() => void);
Eif (handleRef.current?.resize) {
handleRef.current.resize();
stop = str().api.onChange.listen(() => handleRef.current?.resize?.());
}
return () => {
unbind();
stop?.();
};
}, [str, polling]);
Eif (inp) return inp({ref: handleRef, multiline, uncontrolled: true});
(rest as any).ref = (el: HTMLInputElement | HTMLTextAreaElement) => {
domRef.current = el;
};
return React.createElement(multiline ? 'textarea' : 'input', rest);
};
|