@douyinfe/semi-ui
Version:
A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.
102 lines (101 loc) • 4.65 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = require("@tiptap/react");
var _react2 = _interopRequireWildcard(require("react"));
var _constants = require("@douyinfe/semi-foundation/lib/cjs/aiChatInput/constants");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const InputSlotComponent = props => {
const {
editor,
node,
getPos
} = props;
const isEmpty = node.textContent === _constants.strings.ZERO_WIDTH_CHAR;
const placeholder = node.attrs.placeholder || '';
/**
* IMPORTANT:
* inputSlot's NodeViewRenderer.update() is intentionally skipped during IME composition
* (see extension/inputSlot/index.tsx). That means React won't re-render when
* editor.view.composing changes.
*
* To avoid placeholder covering the live IME text, we listen to DOM
* composition events and drive a local state update.
*/
const [hidePlaceholderInComposition, setHidePlaceholderInComposition] = (0, _react2.useState)(false);
const isSelectionInsideThisSlot = (0, _react2.useCallback)(() => {
if (!editor || typeof getPos !== 'function') {
return false;
}
const pos = getPos();
const {
from,
to
} = editor.state.selection;
// getPos() returns the position before this node.
// Selection inside the node should be within (pos, pos + node.nodeSize).
return from > pos && to < pos + node.nodeSize;
}, [editor, getPos, node.nodeSize]);
(0, _react2.useEffect)(() => {
var _a;
if (!((_a = editor === null || editor === void 0 ? void 0 : editor.view) === null || _a === void 0 ? void 0 : _a.dom)) {
return undefined;
}
const onCompositionStart = () => {
if (isSelectionInsideThisSlot()) {
setHidePlaceholderInComposition(true);
}
};
const onCompositionEnd = () => {
setHidePlaceholderInComposition(false);
};
// Use capture to ensure we catch events even when ProseMirror stops propagation.
const dom = editor.view.dom;
dom.addEventListener('compositionstart', onCompositionStart, true);
dom.addEventListener('compositionend', onCompositionEnd, true);
dom.addEventListener('compositioncancel', onCompositionEnd, true);
return () => {
dom.removeEventListener('compositionstart', onCompositionStart, true);
dom.removeEventListener('compositionend', onCompositionEnd, true);
dom.removeEventListener('compositioncancel', onCompositionEnd, true);
};
}, [editor, isSelectionInsideThisSlot]);
// Hide placeholder when composing to avoid covering IME input.
const shouldShowPlaceholder = isEmpty && !hidePlaceholderInComposition;
const placeholderRef = (0, _react2.useRef)(null);
const [placeholderWidth, setPlaceholderWidth] = (0, _react2.useState)(undefined);
(0, _react2.useLayoutEffect)(() => {
if (shouldShowPlaceholder && placeholderRef.current) {
const timer = setTimeout(() => {
var _a;
setPlaceholderWidth((_a = placeholderRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth);
});
return () => {
clearTimeout(timer);
};
}
return undefined;
}, [shouldShowPlaceholder, placeholder]);
return /*#__PURE__*/_react2.default.createElement(_react.NodeViewWrapper, {
as: "span",
className: "input-slot",
style: {
minWidth: shouldShowPlaceholder && placeholderWidth ? `${placeholderWidth}px` : undefined
}
}, /*#__PURE__*/_react2.default.createElement("span", {
ref: placeholderRef,
"data-placeholder": true,
contentEditable: false,
className: "input-slot-placeholder",
style: {
display: shouldShowPlaceholder ? undefined : 'none'
}
}, placeholder), /*#__PURE__*/_react2.default.createElement(_react.NodeViewContent, {
as: "span",
className: "content"
}));
};
var _default = exports.default = InputSlotComponent;