mirror of
https://github.com/docmost/docmost.git
synced 2026-07-15 02:26:47 +10:00
fix(base): place caret at end instead of selecting all when editing cells
This commit is contained in:
@@ -76,6 +76,7 @@ export const GridCell = memo(function GridCell({
|
||||
|
||||
const tapStartRef = useRef<{ x: number; y: number } | null>(null);
|
||||
const suppressClickRef = useRef(false);
|
||||
const expandClickGuardRef = useRef(false);
|
||||
|
||||
const handleDoubleClick = useCallback(() => {
|
||||
if (!property || isRowNumber) return;
|
||||
@@ -121,6 +122,7 @@ export const GridCell = memo(function GridCell({
|
||||
|
||||
const handlePointerDown = useCallback(
|
||||
(e: React.PointerEvent<HTMLDivElement>) => {
|
||||
expandClickGuardRef.current = false;
|
||||
if (e.pointerType === "mouse") return;
|
||||
suppressClickRef.current = false;
|
||||
tapStartRef.current = { x: e.clientX, y: e.clientY };
|
||||
@@ -140,11 +142,18 @@ export const GridCell = memo(function GridCell({
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if ((e.target as HTMLElement).closest("button, a, input")) return;
|
||||
const target = e.target as HTMLElement;
|
||||
if (onExpandRow && target.closest("[data-base-row-expand]")) {
|
||||
suppressClickRef.current = true;
|
||||
expandClickGuardRef.current = true;
|
||||
onExpandRow(rowId);
|
||||
return;
|
||||
}
|
||||
if (target.closest("button, a, input")) return;
|
||||
suppressClickRef.current = true;
|
||||
handleDoubleClick();
|
||||
},
|
||||
[handleDoubleClick],
|
||||
[handleDoubleClick, onExpandRow, rowId],
|
||||
);
|
||||
|
||||
const handlePointerCancel = useCallback(() => {
|
||||
@@ -262,8 +271,15 @@ export const GridCell = memo(function GridCell({
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
data-base-row-expand=""
|
||||
className={classes.rowExpandButton}
|
||||
onClick={() => onExpandRow(rowId)}
|
||||
onClick={() => {
|
||||
if (expandClickGuardRef.current) {
|
||||
expandClickGuardRef.current = false;
|
||||
return;
|
||||
}
|
||||
onExpandRow(rowId);
|
||||
}}
|
||||
onDoubleClick={(e) => e.stopPropagation()}
|
||||
aria-label={t("Expand row {{number}}", { number: rowIndex + 1 })}
|
||||
>
|
||||
|
||||
@@ -52,6 +52,9 @@ export function CreatePropertyPopover({ pageId, properties, onPropertyCreated, r
|
||||
const [dropdownNode, setDropdownNode] = useState<HTMLDivElement | null>(null);
|
||||
const nameInputRef = useRef<HTMLInputElement>(null);
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const [position, setPosition] = useState<"bottom-start" | "top-start">(
|
||||
"bottom-start",
|
||||
);
|
||||
|
||||
const createPropertyMutation = useCreatePropertyMutation();
|
||||
|
||||
@@ -95,10 +98,20 @@ export function CreatePropertyPopover({ pageId, properties, onPropertyCreated, r
|
||||
setTypeOptions({});
|
||||
}, []);
|
||||
|
||||
const handleOpen = useCallback(() => {
|
||||
resetState();
|
||||
setOpened(true);
|
||||
}, [resetState]);
|
||||
const handleOpen = useCallback(
|
||||
(event?: React.SyntheticEvent) => {
|
||||
resetState();
|
||||
const trigger = event?.currentTarget as HTMLElement | undefined;
|
||||
if (trigger) {
|
||||
const rect = trigger.getBoundingClientRect();
|
||||
const spaceAbove = rect.top;
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
setPosition(spaceAbove > spaceBelow ? "top-start" : "bottom-start");
|
||||
}
|
||||
setOpened(true);
|
||||
},
|
||||
[resetState],
|
||||
);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
// Don't reset state here: resetting mid-close flashes the type picker.
|
||||
@@ -217,13 +230,13 @@ export function CreatePropertyPopover({ pageId, properties, onPropertyCreated, r
|
||||
onChange={(o) => {
|
||||
if (!o) attemptClose();
|
||||
}}
|
||||
position="bottom-start"
|
||||
position={position}
|
||||
shadow="md"
|
||||
closeOnClickOutside
|
||||
closeOnEscape={false}
|
||||
withinPortal
|
||||
middlewares={{
|
||||
flip: true,
|
||||
flip: false,
|
||||
shift: true,
|
||||
size: {
|
||||
padding: 8,
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
import {
|
||||
IBaseProperty,
|
||||
BasePropertyType,
|
||||
TypeOptions,
|
||||
SelectTypeOptions,
|
||||
} from "@/ee/base/types/base.types";
|
||||
import { useAtom } from "jotai";
|
||||
import { propertyMenuCloseRequestAtomFamily } from "@/ee/base/atoms/base-atoms";
|
||||
@@ -36,7 +38,11 @@ import {
|
||||
NON_USER_TARGET_TYPES,
|
||||
} from "./conversion-warning";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { isSystemPropertyType, propertyTypes } from "@/ee/base/property-types/property-type.registry";
|
||||
import {
|
||||
isSystemPropertyType,
|
||||
propertyTypes,
|
||||
defaultTypeOptionsFor,
|
||||
} from "@/ee/base/property-types/property-type.registry";
|
||||
import cellClasses from "@/ee/base/styles/cells.module.css";
|
||||
import classes from "@/ee/base/styles/property.module.css";
|
||||
|
||||
@@ -58,6 +64,31 @@ type MenuPanel =
|
||||
| "confirmDelete"
|
||||
| "confirmDiscard";
|
||||
|
||||
const CHOICE_TYPES = new Set<BasePropertyType>([
|
||||
"select",
|
||||
"multiSelect",
|
||||
"status",
|
||||
]);
|
||||
|
||||
function typeOptionsForConversion(
|
||||
source: IBaseProperty,
|
||||
target: BasePropertyType,
|
||||
): TypeOptions {
|
||||
if (!CHOICE_TYPES.has(source.type) || !CHOICE_TYPES.has(target)) {
|
||||
return defaultTypeOptionsFor(target);
|
||||
}
|
||||
const opts = source.typeOptions as SelectTypeOptions | undefined;
|
||||
const choices = opts?.choices ?? [];
|
||||
const choiceOrder = opts?.choiceOrder?.length
|
||||
? opts.choiceOrder
|
||||
: choices.map((c) => c.id);
|
||||
const carried: SelectTypeOptions = { choices, choiceOrder };
|
||||
if (target === "status") {
|
||||
carried.defaultValue = choices[0]?.id ?? null;
|
||||
}
|
||||
return carried;
|
||||
}
|
||||
|
||||
export function PropertyMenuContent({
|
||||
property,
|
||||
opened,
|
||||
@@ -185,16 +216,10 @@ export function PropertyMenuContent({
|
||||
propertyId: property.id,
|
||||
pageId: property.pageId,
|
||||
type: pendingTargetType,
|
||||
typeOptions: {},
|
||||
typeOptions: typeOptionsForConversion(property, pendingTargetType),
|
||||
});
|
||||
onClose();
|
||||
}, [
|
||||
pendingTargetType,
|
||||
property.id,
|
||||
property.pageId,
|
||||
updatePropertyMutation,
|
||||
onClose,
|
||||
]);
|
||||
}, [pendingTargetType, property, updatePropertyMutation, onClose]);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
deletePropertyMutation.mutate({
|
||||
|
||||
@@ -63,8 +63,12 @@ export function useEditableTextCell({
|
||||
} else {
|
||||
setDraft(toDraftRef.current(value));
|
||||
requestAnimationFrame(() => {
|
||||
inputRef.current?.focus();
|
||||
inputRef.current?.select();
|
||||
const el = inputRef.current;
|
||||
if (el) {
|
||||
el.focus();
|
||||
const len = el.value.length;
|
||||
el.setSelectionRange(len, len);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user