UNPKG

alinea

Version:
441 lines (437 loc) 14.7 kB
import { CSS, DndContext, DragOverlay, KeyboardSensor, LayoutMeasuringStrategy, PointerSensor, SortableContext, closestCenter, defaultAnimateLayoutChanges, defaultDropAnimation, sortableKeyboardCoordinates, useSensor, useSensors, useSortable, verticalListSortingStrategy } from "../../chunks/chunk-TPUTTEHF.js"; import { dist_default } from "../../chunks/chunk-A5O3N2GS.js"; import { atomWithStorage } from "../../chunks/chunk-WDCPVJJC.js"; import { useAtom } from "../../chunks/chunk-TOJF2G3X.js"; import "../../chunks/chunk-WJ67RR7S.js"; import "../../chunks/chunk-NZLE2WMY.js"; // src/field/list/ListField.view.tsx import { getType } from "alinea/core/Internal"; import { ListRow } from "alinea/core/shape/ListShape"; import { Type } from "alinea/core/Type"; import { entries } from "alinea/core/util/Objects"; import { FormRow } from "alinea/dashboard/atoms/FormAtoms"; import { InputForm } from "alinea/dashboard/editor/InputForm"; import { useField } from "alinea/dashboard/editor/UseField"; import { Create } from "alinea/dashboard/view/Create"; import { IconButton } from "alinea/dashboard/view/IconButton"; import { InputLabel } from "alinea/dashboard/view/InputLabel"; import { Icon, TextLabel } from "alinea/ui"; import { IcBaselineContentCopy } from "alinea/ui/icons/IcBaselineContentCopy"; import { IcBaselineContentPasteGo } from "alinea/ui/icons/IcBaselineContentPasteGo"; import { IcOutlineList } from "alinea/ui/icons/IcOutlineList"; import { IcRoundAdd } from "alinea/ui/icons/IcRoundAdd"; import { IcRoundArrowDownward } from "alinea/ui/icons/IcRoundArrowDownward"; import { IcRoundArrowUpward } from "alinea/ui/icons/IcRoundArrowUpward"; import { IcRoundClose } from "alinea/ui/icons/IcRoundClose"; import { IcRoundDragHandle } from "alinea/ui/icons/IcRoundDragHandle"; import { IcRoundKeyboardArrowDown } from "alinea/ui/icons/IcRoundKeyboardArrowDown"; import { IcRoundKeyboardArrowUp } from "alinea/ui/icons/IcRoundKeyboardArrowUp"; import { Sink } from "alinea/ui/Sink"; import { useState } from "react"; // src/field/list/ListField.module.scss var ListField_module_default = { "root": "alinea-ListField", "root-inner": "alinea-ListField-inner", "rootInner": "alinea-ListField-inner", "row": "alinea-ListField-row", "is-dragging": "alinea-ListField-is-dragging", "isDragging": "alinea-ListField-is-dragging", "is-overlay": "alinea-ListField-is-overlay", "isOverlay": "alinea-ListField-is-overlay", "row-header-title": "alinea-ListField-row-header-title", "rowHeaderTitle": "alinea-ListField-row-header-title", "insert": "alinea-ListField-insert", "is-first": "alinea-ListField-is-first", "isFirst": "alinea-ListField-is-first", "is-open": "alinea-ListField-is-open", "isOpen": "alinea-ListField-is-open", "insert-icon": "alinea-ListField-insert-icon", "insertIcon": "alinea-ListField-insert-icon", "create": "alinea-ListField-create", "is-inline": "alinea-ListField-is-inline", "isInline": "alinea-ListField-is-inline" }; // src/field/list/ListField.view.tsx import { Fragment, jsx, jsxs } from "react/jsx-runtime"; var styles = dist_default(ListField_module_default); var copyAtom = atomWithStorage( "@alinea/copypaste", void 0 ); function animateLayoutChanges(args) { const { isSorting, wasSorting } = args; if (isSorting || wasSorting) return defaultAnimateLayoutChanges(args); return true; } function ListInputRowSortable(props) { const { onCreate, onPaste } = props; const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ animateLayoutChanges, id: props.row._id }); const style = { transform: CSS.Transform.toString(transform), transition: transition || void 0 }; return /* @__PURE__ */ jsx( ListInputRow, { ...props, rootRef: setNodeRef, style, handle: listeners, ...attributes, isDragging, onCreate, onPaste } ); } function ListInputRow({ row, schema, onMove, onDelete, onCopyBlock, handle, rootRef, isDragging, isDragOverlay, readOnly, onCreate, onPasteBlock, firstRow, lastRow, isFolded, toggleFold, ...rest }) { const type = schema[row[ListRow.type]]; const [showInsert, setShowInsert] = useState(false); if (!type) return null; return /* @__PURE__ */ jsxs( "div", { className: styles.row({ dragging: isDragging, overlay: isDragOverlay }), ref: rootRef, ...rest, children: [ !readOnly && !isDragOverlay && /* @__PURE__ */ jsx( ListInsertRow, { open: showInsert, first: Boolean(firstRow), onInsert: () => setShowInsert(!showInsert) } ), showInsert && /* @__PURE__ */ jsx( ListCreateRow, { inline: true, schema, onCreate: (type2) => { onCreate(type2); setShowInsert(false); }, onPaste: (data) => { if (onPasteBlock) onPasteBlock(data); setShowInsert(false); } } ), /* @__PURE__ */ jsxs(Sink.Header, { children: [ /* @__PURE__ */ jsx(Sink.Options, { style: { zIndex: 1 }, children: /* @__PURE__ */ jsx( IconButton, { icon: getType(type).icon || IcRoundDragHandle, ...handle, style: { cursor: handle ? "grab" : "grabbing" }, title: "Drag and drop to reorder" } ) }), /* @__PURE__ */ jsx(Sink.Title, { children: /* @__PURE__ */ jsx( TextLabel, { label: Type.label(type), className: styles.row.header.title() } ) }), /* @__PURE__ */ jsxs(Sink.Options, { children: [ isFolded !== void 0 && /* @__PURE__ */ jsx( IconButton, { icon: isFolded ? IcRoundKeyboardArrowDown : IcRoundKeyboardArrowUp, onClick: toggleFold, title: isFolded ? "Expand" : "Fold", disabled: isDragOverlay } ), onCopyBlock !== void 0 && /* @__PURE__ */ jsx( IconButton, { icon: IcBaselineContentCopy, onClick: () => onCopyBlock(), title: "Copy block", disabled: isDragOverlay } ), !readOnly && /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx( IconButton, { icon: IcRoundArrowUpward, onClick: () => onMove?.(-1), title: "Move up one position", disabled: firstRow || isDragOverlay } ), /* @__PURE__ */ jsx( IconButton, { icon: IcRoundArrowDownward, onClick: () => onMove?.(1), title: "Move down one position", disabled: lastRow || isDragOverlay } ), /* @__PURE__ */ jsx( IconButton, { icon: IcRoundClose, onClick: onDelete, title: "Delete block", disabled: isDragOverlay } ) ] }) ] }) ] }), !isFolded && /* @__PURE__ */ jsx(Sink.Content, { children: /* @__PURE__ */ jsx(InputForm, { type }) }) ] } ); } function ListCreateRow({ schema, readOnly, inline, onCreate, onPaste }) { const [pasted] = useAtom(copyAtom); const canPaste = pasted && entries(schema).some(([key]) => key === pasted._type); return /* @__PURE__ */ jsx("div", { className: styles.create({ inline }), children: /* @__PURE__ */ jsxs(Create.Root, { disabled: readOnly, children: [ entries(schema).map(([key, type]) => { return /* @__PURE__ */ jsx( Create.Button, { icon: getType(type).icon, onClick: () => onCreate(key), children: /* @__PURE__ */ jsx(TextLabel, { label: Type.label(type) }) }, key ); }), canPaste && /* @__PURE__ */ jsx( Create.Button, { icon: IcBaselineContentPasteGo, onClick: () => onPaste(pasted), mod: "paste", children: /* @__PURE__ */ jsx(TextLabel, { label: "Paste block" }) } ) ] }) }); } function ListInsertRow({ first, open, onInsert }) { return /* @__PURE__ */ jsx("div", { className: styles.insert({ open, first }), children: /* @__PURE__ */ jsx( "button", { className: styles.insert.icon(), onClick: onInsert, title: "Insert new block", type: "button", children: /* @__PURE__ */ jsx(Icon, { icon: open ? IcRoundKeyboardArrowUp : IcRoundAdd }) } ) }); } var layoutMeasuringConfig = { strategy: LayoutMeasuringStrategy.Always }; function ListInput({ field }) { const { options, value, mutator, error } = useField(field); const { schema, readOnly } = options; const rows = value; const ids = rows.map((row) => row._id); const [, setPasted] = useAtom(copyAtom); const [dragging, setDragging] = useState(null); const [foldedItems, setFoldedItems] = useState(/* @__PURE__ */ new Set()); const sensors = useSensors( useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) ); function handleDragStart(event) { const { active } = event; setDragging(rows.find((row) => row._id === active.id) || null); } function handleDragEnd(event) { const { active, over } = event; if (!over || active.id === over.id) return; if (!readOnly) mutator.move(ids.indexOf(active.id), ids.indexOf(over.id)); setDragging(null); } const isFolded = rows.every((row) => foldedItems.has(row._id)); const toggleFold = () => setFoldedItems(new Set(isFolded ? [] : rows.map((row) => row._id))); return /* @__PURE__ */ jsx( DndContext, { sensors, collisionDetection: closestCenter, onDragStart: handleDragStart, onDragEnd: handleDragEnd, layoutMeasuring: layoutMeasuringConfig, children: /* @__PURE__ */ jsx( InputLabel, { ...options, error, icon: IcOutlineList, isFolded: rows.length === 0 ? void 0 : isFolded, toggleFold, children: /* @__PURE__ */ jsx("div", { className: styles.root(), children: /* @__PURE__ */ jsxs("div", { className: styles.root.inner({ inline: options.inline }), children: [ /* @__PURE__ */ jsx(SortableContext, { items: ids, strategy: verticalListSortingStrategy, children: /* @__PURE__ */ jsxs(Sink.Root, { children: [ rows.map((row, i) => { const type = options.schema[row[ListRow.type]]; return /* @__PURE__ */ jsx( FormRow, { field, rowId: row._id, type, readOnly, children: /* @__PURE__ */ jsx( ListInputRowSortable, { isFolded: foldedItems.has(row._id), row, schema, readOnly, onCopyBlock: () => { const data = mutator.read(row._id); setPasted(data); }, onMove: (direction) => { if (readOnly) return; mutator.move(i, i + direction); }, onDelete: () => { if (readOnly) return; mutator.remove(row._id); }, onCreate: (type2) => { if (readOnly) return; mutator.push({ _type: type2 }, i); }, onPasteBlock: (data) => { if (readOnly) return; const { _id, _index, ...rest } = data; mutator.push(rest, i); }, toggleFold: () => { setFoldedItems((current) => { const result = new Set(current); if (result.has(row._id)) result.delete(row._id); else result.add(row._id); return result; }); }, firstRow: i === 0, lastRow: i === rows.length - 1 } ) }, row._id ); }), /* @__PURE__ */ jsx( ListCreateRow, { schema, readOnly, onPaste: (data) => { const { _id, _index, ...rest } = data; mutator.push(rest); }, onCreate: (type, data) => { if (readOnly) return; mutator.push({ _type: type }); } } ) ] }) }), /* @__PURE__ */ jsx( DragOverlay, { dropAnimation: { ...defaultDropAnimation, dragSourceOpacity: 0.5 }, children: dragging ? /* @__PURE__ */ jsx( FormRow, { field, rowId: dragging._id, type: options.schema[dragging[ListRow.type]], readOnly, children: /* @__PURE__ */ jsx( ListInputRow, { row: dragging, schema, isDragOverlay: true, isFolded: foldedItems.has(dragging._id), onCopyBlock: () => { } }, "overlay" ) }, "overlay" ) : null } ) ] }) }) } ) } ); } export { ListInput };