@drawnagency/primitives 0.1.106 → 0.1.107
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-QLBHERK6.js → chunk-IPKZ5FU6.js} +74 -24
- package/dist/{chunk-Z7VXESB6.js → chunk-JIQBLG6Y.js} +6 -3
- package/dist/components/sections/Container/Container.d.ts.map +1 -1
- package/dist/components/sections/brand-guide.js +1 -1
- package/dist/components/sections/register-schemas.js +2 -2
- package/dist/components/sections/register.js +2 -2
- package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -1
- package/dist/components/shell/SectionTypePicker.d.ts.map +1 -1
- package/dist/lib/container-ops.d.ts.map +1 -1
- package/dist/lib/insert-menu.d.ts +7 -3
- package/dist/lib/insert-menu.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/sections/Container/Container.tsx +12 -2
- package/src/components/sections/IconList/IconList.tsx +75 -22
- package/src/components/shell/EditorShell.tsx +4 -1
- package/src/components/shell/MediaLibraryModal.tsx +42 -5
- package/src/components/shell/SectionTypePicker.tsx +28 -2
- package/src/lib/container-ops.ts +6 -3
- package/src/lib/insert-menu.ts +38 -4
|
@@ -410,7 +410,7 @@ import { z as z3 } from "zod";
|
|
|
410
410
|
import { List, Columns2 } from "lucide-react";
|
|
411
411
|
|
|
412
412
|
// src/components/sections/IconList/IconList.tsx
|
|
413
|
-
import { useRef as useRef3, useEffect as useEffect4, useState as useState4, useCallback } from "react";
|
|
413
|
+
import { useRef as useRef3, useEffect as useEffect4, useState as useState4, useCallback, useId } from "react";
|
|
414
414
|
import { Check as Check2, CopyPlus, Pencil, X as X2 } from "lucide-react";
|
|
415
415
|
|
|
416
416
|
// src/components/primitives/IconPicker.tsx
|
|
@@ -670,12 +670,47 @@ function EditIconList({
|
|
|
670
670
|
iconClassName,
|
|
671
671
|
onItemsChange
|
|
672
672
|
}) {
|
|
673
|
-
const
|
|
673
|
+
const listId = useId();
|
|
674
|
+
const { wrappedItems, onReorder, onAdd, onInsertItem, onRemove } = useEditableCollection({
|
|
674
675
|
items,
|
|
675
676
|
onChange: onItemsChange,
|
|
676
677
|
createItem: () => ({ label: "", text: "" })
|
|
677
678
|
});
|
|
678
679
|
const [dragState, setDragState] = useState4({ sourceId: null, targetId: null });
|
|
680
|
+
const cfgRef = useRef3({ listId, onReorder, onRemove, onInsertItem });
|
|
681
|
+
cfgRef.current = { listId, onReorder, onRemove, onInsertItem };
|
|
682
|
+
useEffect4(() => {
|
|
683
|
+
let cleanup;
|
|
684
|
+
let cancelled = false;
|
|
685
|
+
import("./adapter-HH47ZPGM.js").then(({ monitorForElements }) => {
|
|
686
|
+
if (cancelled) return;
|
|
687
|
+
cleanup = monitorForElements({
|
|
688
|
+
onDrop: ({ source, location }) => {
|
|
689
|
+
if (source.data.dragType !== "icon-list-row") return;
|
|
690
|
+
setDragState({ sourceId: null, targetId: null });
|
|
691
|
+
const { listId: my, onReorder: onReorder2, onRemove: onRemove2, onInsertItem: onInsertItem2 } = cfgRef.current;
|
|
692
|
+
const target = location.current.dropTargets[0];
|
|
693
|
+
if (!target) return;
|
|
694
|
+
const srcListId = source.data.listId;
|
|
695
|
+
const dstListId = target.data.listId;
|
|
696
|
+
const fromIndex = source.data.index;
|
|
697
|
+
const targetIndex = target.data.index;
|
|
698
|
+
if (srcListId === my && dstListId === my) {
|
|
699
|
+
const toIndex = target.data.trailing ? targetIndex - 1 : targetIndex;
|
|
700
|
+
if (fromIndex !== toIndex) onReorder2(fromIndex, toIndex);
|
|
701
|
+
} else if (srcListId === my) {
|
|
702
|
+
onRemove2(source.data.id);
|
|
703
|
+
} else if (dstListId === my) {
|
|
704
|
+
onInsertItem2(targetIndex, source.data.item);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
});
|
|
709
|
+
return () => {
|
|
710
|
+
cancelled = true;
|
|
711
|
+
cleanup?.();
|
|
712
|
+
};
|
|
713
|
+
}, []);
|
|
679
714
|
const updateItem = useCallback(
|
|
680
715
|
(index, patch) => {
|
|
681
716
|
const updated = wrappedItems.map(
|
|
@@ -703,6 +738,7 @@ function EditIconList({
|
|
|
703
738
|
EditableRow,
|
|
704
739
|
{
|
|
705
740
|
id: wrapped.id,
|
|
741
|
+
listId,
|
|
706
742
|
index,
|
|
707
743
|
item: wrapped.data,
|
|
708
744
|
defaultIcon,
|
|
@@ -715,7 +751,6 @@ function EditIconList({
|
|
|
715
751
|
iconClassName,
|
|
716
752
|
dragState,
|
|
717
753
|
setDragState,
|
|
718
|
-
onReorder,
|
|
719
754
|
onRemove,
|
|
720
755
|
onUpdateItem: (patch) => updateItem(index, patch),
|
|
721
756
|
onDuplicate: () => duplicateItem(index)
|
|
@@ -725,10 +760,10 @@ function EditIconList({
|
|
|
725
760
|
/* @__PURE__ */ jsx8(
|
|
726
761
|
TrailingDropZone,
|
|
727
762
|
{
|
|
763
|
+
listId,
|
|
728
764
|
index: wrappedItems.length,
|
|
729
765
|
dragState,
|
|
730
|
-
setDragState
|
|
731
|
-
onReorder
|
|
766
|
+
setDragState
|
|
732
767
|
}
|
|
733
768
|
),
|
|
734
769
|
/* @__PURE__ */ jsx8(
|
|
@@ -746,6 +781,7 @@ function EditIconList({
|
|
|
746
781
|
}
|
|
747
782
|
function EditableRow({
|
|
748
783
|
id,
|
|
784
|
+
listId,
|
|
749
785
|
index,
|
|
750
786
|
item,
|
|
751
787
|
defaultIcon,
|
|
@@ -758,13 +794,14 @@ function EditableRow({
|
|
|
758
794
|
iconClassName,
|
|
759
795
|
dragState,
|
|
760
796
|
setDragState,
|
|
761
|
-
onReorder,
|
|
762
797
|
onRemove,
|
|
763
798
|
onUpdateItem,
|
|
764
799
|
onDuplicate
|
|
765
800
|
}) {
|
|
766
801
|
const rowRef = useRef3(null);
|
|
767
802
|
const handleRef = useRef3(null);
|
|
803
|
+
const itemRef = useRef3(item);
|
|
804
|
+
itemRef.current = item;
|
|
768
805
|
const [iconHover, setIconHover] = useState4(false);
|
|
769
806
|
const [pickerOpen, setPickerOpen] = useState4(false);
|
|
770
807
|
const [dodontOpen, setDodontOpen] = useState4(false);
|
|
@@ -782,22 +819,21 @@ function EditableRow({
|
|
|
782
819
|
if (cancelled) return;
|
|
783
820
|
const cleanupDraggable = draggable({
|
|
784
821
|
element: handle,
|
|
785
|
-
|
|
822
|
+
// `item` rides along so a destination list can insert it verbatim —
|
|
823
|
+
// persisted icon-list items have no ids, only positions. Read from a ref
|
|
824
|
+
// so typing in the row doesn't re-register the drag mid-gesture.
|
|
825
|
+
getInitialData: () => ({ dragType: "icon-list-row", listId, id, index, item: itemRef.current }),
|
|
786
826
|
onDragStart: () => setDragState({ sourceId: id, targetId: null }),
|
|
787
827
|
onDrop: () => setDragState({ sourceId: null, targetId: null })
|
|
788
828
|
});
|
|
789
829
|
const cleanupDropTarget = dropTargetForElements({
|
|
790
830
|
element: row,
|
|
791
831
|
canDrop: ({ source }) => source.data.dragType === "icon-list-row",
|
|
792
|
-
getData: () => ({ id, index }),
|
|
832
|
+
getData: () => ({ listId, id, index }),
|
|
793
833
|
onDragEnter: () => setDragState((prev) => ({ ...prev, targetId: id })),
|
|
794
834
|
onDragLeave: () => setDragState(
|
|
795
835
|
(prev) => prev.targetId === id ? { ...prev, targetId: null } : prev
|
|
796
|
-
)
|
|
797
|
-
onDrop: ({ source }) => {
|
|
798
|
-
const fromIndex = source.data.index;
|
|
799
|
-
if (fromIndex !== index) onReorder(fromIndex, index);
|
|
800
|
-
}
|
|
836
|
+
)
|
|
801
837
|
});
|
|
802
838
|
cleanup = () => {
|
|
803
839
|
cleanupDraggable();
|
|
@@ -808,7 +844,7 @@ function EditableRow({
|
|
|
808
844
|
cancelled = true;
|
|
809
845
|
cleanup?.();
|
|
810
846
|
};
|
|
811
|
-
}, [id, index,
|
|
847
|
+
}, [id, index, listId, setDragState]);
|
|
812
848
|
const isDropTarget = dragState.targetId === id && dragState.sourceId !== id;
|
|
813
849
|
const onEditableActivity = (e) => {
|
|
814
850
|
const t = e.target;
|
|
@@ -964,10 +1000,10 @@ function EditableRow({
|
|
|
964
1000
|
);
|
|
965
1001
|
}
|
|
966
1002
|
function TrailingDropZone({
|
|
1003
|
+
listId,
|
|
967
1004
|
index,
|
|
968
1005
|
dragState,
|
|
969
|
-
setDragState
|
|
970
|
-
onReorder
|
|
1006
|
+
setDragState
|
|
971
1007
|
}) {
|
|
972
1008
|
const ref = useRef3(null);
|
|
973
1009
|
const trailingId = "__trailing__";
|
|
@@ -981,23 +1017,19 @@ function TrailingDropZone({
|
|
|
981
1017
|
cleanup = dropTargetForElements({
|
|
982
1018
|
element: el,
|
|
983
1019
|
canDrop: ({ source }) => source.data.dragType === "icon-list-row",
|
|
984
|
-
getData: () => ({ id: trailingId, index }),
|
|
1020
|
+
getData: () => ({ listId, id: trailingId, index, trailing: true }),
|
|
985
1021
|
onDragEnter: () => setDragState((prev) => ({ ...prev, targetId: trailingId })),
|
|
986
1022
|
onDragLeave: () => setDragState(
|
|
987
1023
|
(prev) => prev.targetId === trailingId ? { ...prev, targetId: null } : prev
|
|
988
|
-
)
|
|
989
|
-
onDrop: ({ source }) => {
|
|
990
|
-
const fromIndex = source.data.index;
|
|
991
|
-
if (fromIndex !== index - 1) onReorder(fromIndex, index - 1);
|
|
992
|
-
}
|
|
1024
|
+
)
|
|
993
1025
|
});
|
|
994
1026
|
});
|
|
995
1027
|
return () => {
|
|
996
1028
|
cancelled = true;
|
|
997
1029
|
cleanup?.();
|
|
998
1030
|
};
|
|
999
|
-
}, [index,
|
|
1000
|
-
const isDropTarget = dragState.targetId === trailingId
|
|
1031
|
+
}, [index, listId, setDragState]);
|
|
1032
|
+
const isDropTarget = dragState.targetId === trailingId;
|
|
1001
1033
|
return /* @__PURE__ */ jsx8(
|
|
1002
1034
|
"div",
|
|
1003
1035
|
{
|
|
@@ -1358,6 +1390,24 @@ function DocumentDropZone({ onDocumentSelected, className }) {
|
|
|
1358
1390
|
);
|
|
1359
1391
|
}
|
|
1360
1392
|
|
|
1393
|
+
// src/lib/insert-menu.ts
|
|
1394
|
+
var PRESET_PREFIX = "__preset:";
|
|
1395
|
+
var INSERT_MENU_ORDER = [
|
|
1396
|
+
"container",
|
|
1397
|
+
"link_heading",
|
|
1398
|
+
"sub_heading",
|
|
1399
|
+
"sub_sub_heading",
|
|
1400
|
+
"prose",
|
|
1401
|
+
"media",
|
|
1402
|
+
"video",
|
|
1403
|
+
"button",
|
|
1404
|
+
"colors",
|
|
1405
|
+
"document",
|
|
1406
|
+
"icon_list",
|
|
1407
|
+
`${PRESET_PREFIX}do_dont_list`,
|
|
1408
|
+
"dodont_media"
|
|
1409
|
+
];
|
|
1410
|
+
|
|
1361
1411
|
// src/lib/container-ops.ts
|
|
1362
1412
|
function getContainerChildren(block) {
|
|
1363
1413
|
const content = block.content;
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
stripHtmlToPlainText,
|
|
10
10
|
trimTrailingSpacers,
|
|
11
11
|
truncate
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-IPKZ5FU6.js";
|
|
13
13
|
import {
|
|
14
14
|
DeleteButton,
|
|
15
15
|
DragHandle,
|
|
@@ -483,7 +483,7 @@ import { z as z5 } from "zod";
|
|
|
483
483
|
import { LayoutTemplate } from "lucide-react";
|
|
484
484
|
|
|
485
485
|
// src/components/sections/Container/Container.tsx
|
|
486
|
-
import "react";
|
|
486
|
+
import { useRef as useRef3 } from "react";
|
|
487
487
|
|
|
488
488
|
// src/components/editor/ChildBlockWrapper.tsx
|
|
489
489
|
import { forwardRef } from "react";
|
|
@@ -859,13 +859,16 @@ function Container({ content, isEditMode, onChange, openModal }) {
|
|
|
859
859
|
content: content.content
|
|
860
860
|
};
|
|
861
861
|
const emit = (nextSection) => onChange?.({ ...content, content: nextSection.content });
|
|
862
|
+
const pendingChildren = useRef3(children);
|
|
863
|
+
pendingChildren.current = children;
|
|
862
864
|
const updateChild = (index, childContent) => {
|
|
863
|
-
const next =
|
|
865
|
+
const next = pendingChildren.current.map((c, i) => {
|
|
864
866
|
if (i !== index) return c;
|
|
865
867
|
const merged = { ...childContent, id: c.id ?? newId() };
|
|
866
868
|
if (c.layout !== void 0) merged.layout = c.layout;
|
|
867
869
|
return merged;
|
|
868
870
|
});
|
|
871
|
+
pendingChildren.current = next;
|
|
869
872
|
onChange?.({ ...content, content: { ...content.content, children: next } });
|
|
870
873
|
};
|
|
871
874
|
const gridClass = cn(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Container.d.ts","sourceRoot":"","sources":["../../../../src/components/sections/Container/Container.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAcnE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AA4GzD,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,gBAAgB,CAAA;KAAE,CAAC;IACpD,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAwFD,wBAAgB,SAAS,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"Container.d.ts","sourceRoot":"","sources":["../../../../src/components/sections/Container/Container.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAcnE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AA4GzD,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,gBAAgB,CAAA;KAAE,CAAC;IACpD,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAwFD,wBAAgB,SAAS,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,cAAc,kDAwSrF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
allSectionDefs
|
|
3
|
-
} from "../../chunk-
|
|
4
|
-
import "../../chunk-
|
|
3
|
+
} from "../../chunk-JIQBLG6Y.js";
|
|
4
|
+
import "../../chunk-IPKZ5FU6.js";
|
|
5
5
|
import "../../chunk-ZHHJVKU7.js";
|
|
6
6
|
import "../../chunk-5THS4K5D.js";
|
|
7
7
|
import "../../chunk-LUAI5DWR.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
allSectionDefs
|
|
3
|
-
} from "../../chunk-
|
|
4
|
-
import "../../chunk-
|
|
3
|
+
} from "../../chunk-JIQBLG6Y.js";
|
|
4
|
+
import "../../chunk-IPKZ5FU6.js";
|
|
5
5
|
import "../../chunk-ZHHJVKU7.js";
|
|
6
6
|
import "../../chunk-5THS4K5D.js";
|
|
7
7
|
import "../../chunk-LUAI5DWR.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MediaLibraryModal.d.ts","sourceRoot":"","sources":["../../../src/components/shell/MediaLibraryModal.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"MediaLibraryModal.d.ts","sourceRoot":"","sources":["../../../src/components/shell/MediaLibraryModal.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SectionTypePicker.d.ts","sourceRoot":"","sources":["../../../src/components/shell/SectionTypePicker.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE1E,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED,UAAU,sBAAsB;IAC9B,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CAC9C;
|
|
1
|
+
{"version":3,"file":"SectionTypePicker.d.ts","sourceRoot":"","sources":["../../../src/components/shell/SectionTypePicker.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE1E,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED,UAAU,sBAAsB;IAC9B,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CAC9C;AAKD,wBAAgB,iBAAiB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,sBAAsB,2CA0DnG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container-ops.d.ts","sourceRoot":"","sources":["../../src/lib/container-ops.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"container-ops.d.ts","sourceRoot":"","sources":["../../src/lib/container-ops.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAYnD,qEAAqE;AACrE,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,EAAE,CAG9D;AAWD,wBAAgB,YAAY,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAOlF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,EAAE,CAMzF;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAMxF;AAED,wBAAgB,aAAa,CAC3B,SAAS,EAAE,OAAO,EAClB,KAAK,EAAE,MAAM,GACZ;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,CAMjD;AAED,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,OAAO,EAClB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,MAAM,GAClB,OAAO,CAOT;AAED,0FAA0F;AAC1F,wBAAgB,eAAe,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAaxF;AAOD,8CAA8C;AAC9C,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEhD;AAED,uCAAuC;AACvC,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,MAAM,GAAG,OAAO,CAEvD;AAED,iGAAiG;AACjG,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE5E;AAED,8FAA8F;AAC9F,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,OAAO,GAAG,OAAO,CAM/D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,OAAO,EAClB,KAAK,EAAE,OAAO,EACd,CAAC,EAAE,MAAM,EACT,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,MAAM,GAClB,OAAO,CAiBT"}
|
|
@@ -9,9 +9,13 @@ export interface InsertMenuOption {
|
|
|
9
9
|
label: string;
|
|
10
10
|
icon?: ReactNode;
|
|
11
11
|
}
|
|
12
|
-
/**
|
|
13
|
-
* sections
|
|
14
|
-
|
|
12
|
+
/** Sort insert-menu entries into `INSERT_MENU_ORDER`, with unlisted entries
|
|
13
|
+
* (custom sections) alphabetical by label at the end. Pure; returns a copy. */
|
|
14
|
+
export declare function sortInsertMenuOptions<T extends InsertMenuOption>(options: T[]): T[];
|
|
15
|
+
/** Every non-spacer section type, plus presets contributed by registered
|
|
16
|
+
* sections, in `INSERT_MENU_ORDER`. Presets appear only when their owning
|
|
17
|
+
* section is registered, so a core-only build (no brand-guide sections) shows
|
|
18
|
+
* no brand-guide presets. */
|
|
15
19
|
export declare function buildInsertMenuOptions(sections: SectionDefinition[]): InsertMenuOption[];
|
|
16
20
|
/** Resolve an Add-menu selection to ready-to-insert content. `newId` mints ids for
|
|
17
21
|
* any nested children a preset creates. Returns null for an unknown type/preset. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"insert-menu.d.ts","sourceRoot":"","sources":["../../src/lib/insert-menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,wEAAwE;AACxE,eAAO,MAAM,aAAa,cAAc,CAAC;AAEzC,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED
|
|
1
|
+
{"version":3,"file":"insert-menu.d.ts","sourceRoot":"","sources":["../../src/lib/insert-menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,wEAAwE;AACxE,eAAO,MAAM,aAAa,cAAc,CAAC;AAEzC,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAsBD;gFACgF;AAChF,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,gBAAgB,EAAE,OAAO,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CASnF;AAED;;;8BAG8B;AAC9B,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,GAAG,gBAAgB,EAAE,CAQxF;AAED;qFACqF;AACrF,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,KAAK,EAAE,MAAM,MAAM,GAClB,cAAc,GAAG,IAAI,CAQvB"}
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ComponentType, type ReactNode } from "react";
|
|
1
|
+
import { useRef, type ComponentType, type ReactNode } from "react";
|
|
2
2
|
import type { Edge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
|
|
3
3
|
import { getSection, type OpenModal } from "../../../lib/registry";
|
|
4
4
|
import { cn } from "../../../lib/cn";
|
|
@@ -243,13 +243,23 @@ export function Container({ content, isEditMode, onChange, openModal }: Containe
|
|
|
243
243
|
// the existing child — an id-less child fails save validation for the whole
|
|
244
244
|
// container. Minting when the existing child has no id self-heals drafts saved
|
|
245
245
|
// by the pre-fix path.
|
|
246
|
+
// Folded onto a ref rather than straight onto `children` so that TWO children
|
|
247
|
+
// updating in the same tick compose instead of the second clobbering the
|
|
248
|
+
// first. That happens for real: dragging an icon-list row between two lists in
|
|
249
|
+
// one container removes from one child and inserts into the other, and both
|
|
250
|
+
// emits carry the whole container's content. Re-seeded from props each render,
|
|
251
|
+
// so a committed update is always the base for the next tick.
|
|
252
|
+
const pendingChildren = useRef(children);
|
|
253
|
+
pendingChildren.current = children;
|
|
254
|
+
|
|
246
255
|
const updateChild = (index: number, childContent: unknown) => {
|
|
247
|
-
const next =
|
|
256
|
+
const next = pendingChildren.current.map((c, i) => {
|
|
248
257
|
if (i !== index) return c;
|
|
249
258
|
const merged = { ...(childContent as Section), id: c.id ?? newId() };
|
|
250
259
|
if (c.layout !== undefined) merged.layout = c.layout;
|
|
251
260
|
return merged;
|
|
252
261
|
});
|
|
262
|
+
pendingChildren.current = next;
|
|
253
263
|
onChange?.({ ...content, content: { ...content.content, children: next } });
|
|
254
264
|
};
|
|
255
265
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useRef, useEffect, useState, useCallback } from "react";
|
|
1
|
+
import { useRef, useEffect, useState, useCallback, useId } from "react";
|
|
2
2
|
import type { SectionContent } from "../../../schemas/sections";
|
|
3
3
|
import { cn } from "../../../lib/cn";
|
|
4
4
|
import { getIcon } from "../../../lib/icons";
|
|
@@ -200,7 +200,8 @@ function EditIconList({
|
|
|
200
200
|
iconClassName: string;
|
|
201
201
|
onItemsChange: (items: IconListItem[]) => void;
|
|
202
202
|
}) {
|
|
203
|
-
const
|
|
203
|
+
const listId = useId();
|
|
204
|
+
const { wrappedItems, onReorder, onAdd, onInsertItem, onRemove } = useEditableCollection({
|
|
204
205
|
items,
|
|
205
206
|
onChange: onItemsChange,
|
|
206
207
|
createItem: () => ({ label: "", text: "" }),
|
|
@@ -211,6 +212,57 @@ function EditIconList({
|
|
|
211
212
|
targetId: string | null;
|
|
212
213
|
}>({ sourceId: null, targetId: null });
|
|
213
214
|
|
|
215
|
+
// The drop transaction lives here, not in the row/trailing drop targets,
|
|
216
|
+
// because this monitor is GLOBAL: every icon list on the page sees the same
|
|
217
|
+
// drop and acts on its own collection — the source removes, the destination
|
|
218
|
+
// inserts, and a same-list drop reorders. Rows only advertise where they are.
|
|
219
|
+
// Handlers are read through a ref so the monitor subscribes exactly once.
|
|
220
|
+
const cfgRef = useRef({ listId, onReorder, onRemove, onInsertItem });
|
|
221
|
+
cfgRef.current = { listId, onReorder, onRemove, onInsertItem };
|
|
222
|
+
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
let cleanup: (() => void) | undefined;
|
|
225
|
+
let cancelled = false;
|
|
226
|
+
|
|
227
|
+
import("@atlaskit/pragmatic-drag-and-drop/element/adapter").then(({ monitorForElements }) => {
|
|
228
|
+
if (cancelled) return;
|
|
229
|
+
cleanup = monitorForElements({
|
|
230
|
+
onDrop: ({ source, location }) => {
|
|
231
|
+
if (source.data.dragType !== "icon-list-row") return;
|
|
232
|
+
// Every list clears its own indicator on any drop — this is what stops
|
|
233
|
+
// the destination's drop line sticking after a cross-list transfer.
|
|
234
|
+
setDragState({ sourceId: null, targetId: null });
|
|
235
|
+
|
|
236
|
+
const { listId: my, onReorder, onRemove, onInsertItem } = cfgRef.current;
|
|
237
|
+
const target = location.current.dropTargets[0];
|
|
238
|
+
if (!target) return;
|
|
239
|
+
const srcListId = source.data.listId as string;
|
|
240
|
+
const dstListId = target.data.listId as string;
|
|
241
|
+
const fromIndex = source.data.index as number;
|
|
242
|
+
const targetIndex = target.data.index as number;
|
|
243
|
+
|
|
244
|
+
if (srcListId === my && dstListId === my) {
|
|
245
|
+
// The trailing zone reports the post-list index; splicing the row out
|
|
246
|
+
// first shifts everything after it down one, hence the -1.
|
|
247
|
+
const toIndex = target.data.trailing ? targetIndex - 1 : targetIndex;
|
|
248
|
+
if (fromIndex !== toIndex) onReorder(fromIndex, toIndex);
|
|
249
|
+
} else if (srcListId === my) {
|
|
250
|
+
onRemove(source.data.id as string);
|
|
251
|
+
} else if (dstListId === my) {
|
|
252
|
+
// Nothing is spliced out of this list, so the target index is the
|
|
253
|
+
// insert position as-is (the trailing zone's index appends).
|
|
254
|
+
onInsertItem(targetIndex, source.data.item as IconListItem);
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
return () => {
|
|
261
|
+
cancelled = true;
|
|
262
|
+
cleanup?.();
|
|
263
|
+
};
|
|
264
|
+
}, []);
|
|
265
|
+
|
|
214
266
|
const updateItem = useCallback(
|
|
215
267
|
(index: number, patch: Partial<IconListItem>) => {
|
|
216
268
|
const updated = wrappedItems.map((w, i) =>
|
|
@@ -244,6 +296,7 @@ function EditIconList({
|
|
|
244
296
|
<EditableRow
|
|
245
297
|
key={wrapped.id}
|
|
246
298
|
id={wrapped.id}
|
|
299
|
+
listId={listId}
|
|
247
300
|
index={index}
|
|
248
301
|
item={wrapped.data}
|
|
249
302
|
defaultIcon={defaultIcon}
|
|
@@ -256,7 +309,6 @@ function EditIconList({
|
|
|
256
309
|
iconClassName={iconClassName}
|
|
257
310
|
dragState={dragState}
|
|
258
311
|
setDragState={setDragState}
|
|
259
|
-
onReorder={onReorder}
|
|
260
312
|
onRemove={onRemove}
|
|
261
313
|
onUpdateItem={(patch) => updateItem(index, patch)}
|
|
262
314
|
onDuplicate={() => duplicateItem(index)}
|
|
@@ -264,10 +316,10 @@ function EditIconList({
|
|
|
264
316
|
))}
|
|
265
317
|
|
|
266
318
|
<TrailingDropZone
|
|
319
|
+
listId={listId}
|
|
267
320
|
index={wrappedItems.length}
|
|
268
321
|
dragState={dragState}
|
|
269
322
|
setDragState={setDragState}
|
|
270
|
-
onReorder={onReorder}
|
|
271
323
|
/>
|
|
272
324
|
|
|
273
325
|
{/* Add button — centered below list, visible on list hover */}
|
|
@@ -285,6 +337,7 @@ function EditIconList({
|
|
|
285
337
|
|
|
286
338
|
function EditableRow({
|
|
287
339
|
id,
|
|
340
|
+
listId,
|
|
288
341
|
index,
|
|
289
342
|
item,
|
|
290
343
|
defaultIcon,
|
|
@@ -297,12 +350,12 @@ function EditableRow({
|
|
|
297
350
|
iconClassName,
|
|
298
351
|
dragState,
|
|
299
352
|
setDragState,
|
|
300
|
-
onReorder,
|
|
301
353
|
onRemove,
|
|
302
354
|
onUpdateItem,
|
|
303
355
|
onDuplicate,
|
|
304
356
|
}: {
|
|
305
357
|
id: string;
|
|
358
|
+
listId: string;
|
|
306
359
|
index: number;
|
|
307
360
|
item: IconListItem;
|
|
308
361
|
defaultIcon: string | null | undefined;
|
|
@@ -315,13 +368,14 @@ function EditableRow({
|
|
|
315
368
|
iconClassName: string;
|
|
316
369
|
dragState: { sourceId: string | null; targetId: string | null };
|
|
317
370
|
setDragState: React.Dispatch<React.SetStateAction<{ sourceId: string | null; targetId: string | null }>>;
|
|
318
|
-
onReorder: (from: number, to: number) => void;
|
|
319
371
|
onRemove: (id: string) => void;
|
|
320
372
|
onUpdateItem: (patch: Partial<IconListItem>) => void;
|
|
321
373
|
onDuplicate: () => void;
|
|
322
374
|
}) {
|
|
323
375
|
const rowRef = useRef<HTMLDivElement>(null);
|
|
324
376
|
const handleRef = useRef<HTMLButtonElement>(null);
|
|
377
|
+
const itemRef = useRef(item);
|
|
378
|
+
itemRef.current = item;
|
|
325
379
|
const [iconHover, setIconHover] = useState(false);
|
|
326
380
|
const [pickerOpen, setPickerOpen] = useState(false);
|
|
327
381
|
const [dodontOpen, setDodontOpen] = useState(false);
|
|
@@ -344,24 +398,25 @@ function EditableRow({
|
|
|
344
398
|
|
|
345
399
|
const cleanupDraggable = draggable({
|
|
346
400
|
element: handle,
|
|
347
|
-
|
|
401
|
+
// `item` rides along so a destination list can insert it verbatim —
|
|
402
|
+
// persisted icon-list items have no ids, only positions. Read from a ref
|
|
403
|
+
// so typing in the row doesn't re-register the drag mid-gesture.
|
|
404
|
+
getInitialData: () => ({ dragType: "icon-list-row", listId, id, index, item: itemRef.current }),
|
|
348
405
|
onDragStart: () => setDragState({ sourceId: id, targetId: null }),
|
|
349
406
|
onDrop: () => setDragState({ sourceId: null, targetId: null }),
|
|
350
407
|
});
|
|
351
408
|
|
|
409
|
+
// Advertise only — EditIconList's monitor performs the move, so that one
|
|
410
|
+
// handler can see both sides of a cross-list transfer.
|
|
352
411
|
const cleanupDropTarget = dropTargetForElements({
|
|
353
412
|
element: row,
|
|
354
413
|
canDrop: ({ source }) => source.data.dragType === "icon-list-row",
|
|
355
|
-
getData: () => ({ id, index }),
|
|
414
|
+
getData: () => ({ listId, id, index }),
|
|
356
415
|
onDragEnter: () => setDragState((prev) => ({ ...prev, targetId: id })),
|
|
357
416
|
onDragLeave: () =>
|
|
358
417
|
setDragState((prev) =>
|
|
359
418
|
prev.targetId === id ? { ...prev, targetId: null } : prev,
|
|
360
419
|
),
|
|
361
|
-
onDrop: ({ source }) => {
|
|
362
|
-
const fromIndex = source.data.index as number;
|
|
363
|
-
if (fromIndex !== index) onReorder(fromIndex, index);
|
|
364
|
-
},
|
|
365
420
|
});
|
|
366
421
|
|
|
367
422
|
cleanup = () => {
|
|
@@ -374,7 +429,7 @@ function EditableRow({
|
|
|
374
429
|
cancelled = true;
|
|
375
430
|
cleanup?.();
|
|
376
431
|
};
|
|
377
|
-
}, [id, index,
|
|
432
|
+
}, [id, index, listId, setDragState]);
|
|
378
433
|
|
|
379
434
|
const isDropTarget = dragState.targetId === id && dragState.sourceId !== id;
|
|
380
435
|
|
|
@@ -525,15 +580,15 @@ function EditableRow({
|
|
|
525
580
|
}
|
|
526
581
|
|
|
527
582
|
function TrailingDropZone({
|
|
583
|
+
listId,
|
|
528
584
|
index,
|
|
529
585
|
dragState,
|
|
530
586
|
setDragState,
|
|
531
|
-
onReorder,
|
|
532
587
|
}: {
|
|
588
|
+
listId: string;
|
|
533
589
|
index: number;
|
|
534
590
|
dragState: { sourceId: string | null; targetId: string | null };
|
|
535
591
|
setDragState: React.Dispatch<React.SetStateAction<{ sourceId: string | null; targetId: string | null }>>;
|
|
536
|
-
onReorder: (fromIndex: number, toIndex: number) => void;
|
|
537
592
|
}) {
|
|
538
593
|
const ref = useRef<HTMLDivElement>(null);
|
|
539
594
|
const trailingId = "__trailing__";
|
|
@@ -551,16 +606,12 @@ function TrailingDropZone({
|
|
|
551
606
|
cleanup = dropTargetForElements({
|
|
552
607
|
element: el,
|
|
553
608
|
canDrop: ({ source }) => source.data.dragType === "icon-list-row",
|
|
554
|
-
getData: () => ({ id: trailingId, index }),
|
|
609
|
+
getData: () => ({ listId, id: trailingId, index, trailing: true }),
|
|
555
610
|
onDragEnter: () => setDragState((prev) => ({ ...prev, targetId: trailingId })),
|
|
556
611
|
onDragLeave: () =>
|
|
557
612
|
setDragState((prev) =>
|
|
558
613
|
prev.targetId === trailingId ? { ...prev, targetId: null } : prev,
|
|
559
614
|
),
|
|
560
|
-
onDrop: ({ source }) => {
|
|
561
|
-
const fromIndex = source.data.index as number;
|
|
562
|
-
if (fromIndex !== index - 1) onReorder(fromIndex, index - 1);
|
|
563
|
-
},
|
|
564
615
|
});
|
|
565
616
|
});
|
|
566
617
|
|
|
@@ -568,9 +619,11 @@ function TrailingDropZone({
|
|
|
568
619
|
cancelled = true;
|
|
569
620
|
cleanup?.();
|
|
570
621
|
};
|
|
571
|
-
}, [index,
|
|
622
|
+
}, [index, listId, setDragState]);
|
|
572
623
|
|
|
573
|
-
|
|
624
|
+
// targetId is only ever set by an icon-list drag entering this zone, so its
|
|
625
|
+
// presence is the whole condition — a cross-list drag leaves sourceId null here.
|
|
626
|
+
const isDropTarget = dragState.targetId === trailingId;
|
|
574
627
|
|
|
575
628
|
return (
|
|
576
629
|
<div
|
|
@@ -2451,7 +2451,10 @@ function EditorContent({
|
|
|
2451
2451
|
}, [onBlockMove]);
|
|
2452
2452
|
|
|
2453
2453
|
return (
|
|
2454
|
-
|
|
2454
|
+
// `.section-wrapper:first-child` zeroes the 2.25rem that every other section
|
|
2455
|
+
// reserves above itself for its hover chrome, so the first section's controls
|
|
2456
|
+
// would sit under the fixed toolbar. Re-reserve it on the list instead.
|
|
2457
|
+
<div className="pt-9">
|
|
2455
2458
|
{displaySections.map(({ section, meta }, index) => {
|
|
2456
2459
|
const definition = getSection(section.type);
|
|
2457
2460
|
if (!definition) {
|
|
@@ -94,7 +94,7 @@ function DocumentTile({ doc, manage, isSelected, refCount, showFolderChip, onCli
|
|
|
94
94
|
isSelected: boolean;
|
|
95
95
|
refCount: number;
|
|
96
96
|
showFolderChip: boolean;
|
|
97
|
-
onClick: () => void;
|
|
97
|
+
onClick: (e: React.MouseEvent) => void;
|
|
98
98
|
}) {
|
|
99
99
|
return (
|
|
100
100
|
<button
|
|
@@ -262,7 +262,7 @@ function UploadZone({ onUpload, onFolderUpload }: {
|
|
|
262
262
|
|
|
263
263
|
function MediaCard({ item, manage, isSelected, dragIds, onClick, children }: {
|
|
264
264
|
item: MediaItem; manage: boolean; isSelected: boolean;
|
|
265
|
-
dragIds: string[]; onClick: () => void; children: React.ReactNode;
|
|
265
|
+
dragIds: string[]; onClick: (e: React.MouseEvent) => void; children: React.ReactNode;
|
|
266
266
|
}) {
|
|
267
267
|
const drag = useMediaDraggable<HTMLDivElement>({
|
|
268
268
|
enabled: manage,
|
|
@@ -446,7 +446,18 @@ export function MediaLibraryModal({
|
|
|
446
446
|
})
|
|
447
447
|
: [];
|
|
448
448
|
|
|
449
|
+
// Visual order of the item grid: documents render before images, and a CSS
|
|
450
|
+
// grid lays both out in source order, so an index range here is exactly the
|
|
451
|
+
// range a shift-click spans on screen — rows included.
|
|
452
|
+
const orderedIds = [...filteredDocuments.map((doc) => doc.id), ...filtered.map((item) => item.id)];
|
|
453
|
+
|
|
454
|
+
// Anchor for shift-click range selection. Stored as an id, not an index:
|
|
455
|
+
// `filtered`/`filteredDocuments` are recomputed every render, so an index
|
|
456
|
+
// would go stale on search, filter, or folder navigation.
|
|
457
|
+
const rangeAnchorId = useRef<string | null>(null);
|
|
458
|
+
|
|
449
459
|
function toggleSelect(id: string) {
|
|
460
|
+
rangeAnchorId.current = id;
|
|
450
461
|
setSelected((prev) => {
|
|
451
462
|
const next = new Set(prev);
|
|
452
463
|
if (next.has(id)) {
|
|
@@ -458,6 +469,32 @@ export function MediaLibraryModal({
|
|
|
458
469
|
});
|
|
459
470
|
}
|
|
460
471
|
|
|
472
|
+
function selectRangeTo(id: string) {
|
|
473
|
+
const anchor = rangeAnchorId.current;
|
|
474
|
+
const from = anchor === null ? -1 : orderedIds.indexOf(anchor);
|
|
475
|
+
const to = orderedIds.indexOf(id);
|
|
476
|
+
// No usable anchor (never clicked, or it was filtered/deleted away) — fall
|
|
477
|
+
// back to a plain toggle so the click is never a no-op.
|
|
478
|
+
if (from === -1 || to === -1) {
|
|
479
|
+
toggleSelect(id);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
const range = orderedIds.slice(Math.min(from, to), Math.max(from, to) + 1);
|
|
483
|
+
// The anchor stays put so the range can be re-extended by shift-clicking
|
|
484
|
+
// again, and the range adds rather than toggles (matching file explorers).
|
|
485
|
+
setSelected((prev) => new Set([...prev, ...range]));
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function handleItemClick(id: string, e: React.MouseEvent) {
|
|
489
|
+
if (e.shiftKey) {
|
|
490
|
+
// Shift-click otherwise drags a native text selection across the grid.
|
|
491
|
+
e.preventDefault();
|
|
492
|
+
selectRangeTo(id);
|
|
493
|
+
} else {
|
|
494
|
+
toggleSelect(id);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
461
498
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
462
499
|
|
|
463
500
|
// One selection, two kinds. Everything downstream — delete dispatch, usage
|
|
@@ -837,7 +874,7 @@ export function MediaLibraryModal({
|
|
|
837
874
|
isSelected={selected.has(doc.id)}
|
|
838
875
|
refCount={documentReferenceCountMap[doc.id] ?? 0}
|
|
839
876
|
showFolderChip={filtering && documentsFolderEnabled}
|
|
840
|
-
onClick={() => (manage ?
|
|
877
|
+
onClick={(e) => (manage ? handleItemClick(doc.id, e) : onSelect(doc.id))}
|
|
841
878
|
/>
|
|
842
879
|
))}
|
|
843
880
|
{filtered.map((item) => {
|
|
@@ -853,11 +890,11 @@ export function MediaLibraryModal({
|
|
|
853
890
|
manage={manage}
|
|
854
891
|
isSelected={isSelected}
|
|
855
892
|
dragIds={dragIds}
|
|
856
|
-
onClick={() => {
|
|
893
|
+
onClick={(e) => {
|
|
857
894
|
if (mode === "select") {
|
|
858
895
|
onSelect(item.id);
|
|
859
896
|
} else {
|
|
860
|
-
|
|
897
|
+
handleItemClick(item.id, e);
|
|
861
898
|
}
|
|
862
899
|
}}
|
|
863
900
|
>
|
|
@@ -13,10 +13,19 @@ interface SectionTypePickerProps {
|
|
|
13
13
|
containerRef?: RefObject<HTMLElement | null>;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
const BUTTON_CLASS =
|
|
17
|
+
"cursor-pointer flex items-center gap-3 rounded-lg border border-base-200 bg-base px-4 py-3 text-left text-sm text-base-contrast-light transition-colors hover:border-base-300 hover:bg-base-accent hover:text-base-contrast";
|
|
18
|
+
|
|
16
19
|
export function SectionTypePicker({ types, onSelect, onClose, containerRef }: SectionTypePickerProps) {
|
|
17
20
|
const panelRef = useRef<HTMLDivElement>(null);
|
|
18
21
|
const dismissRef = containerRef ?? panelRef;
|
|
19
22
|
|
|
23
|
+
// Container is a layout choice rather than a content block, so it gets its own
|
|
24
|
+
// full-width row above a divider. It's absent from the container child picker
|
|
25
|
+
// (one level of nesting), in which case there's no lead row and no divider.
|
|
26
|
+
const lead = types.find((t) => t.type === "container");
|
|
27
|
+
const rest = types.filter((t) => t !== lead);
|
|
28
|
+
|
|
20
29
|
useEffect(() => {
|
|
21
30
|
function handleMouseDown(e: MouseEvent) {
|
|
22
31
|
if (dismissRef.current && !dismissRef.current.contains(e.target as Node)) {
|
|
@@ -32,10 +41,27 @@ export function SectionTypePicker({ types, onSelect, onClose, containerRef }: Se
|
|
|
32
41
|
ref={panelRef}
|
|
33
42
|
className="z-50 mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3"
|
|
34
43
|
>
|
|
35
|
-
{
|
|
44
|
+
{lead && (
|
|
45
|
+
<>
|
|
46
|
+
<button
|
|
47
|
+
key={lead.type}
|
|
48
|
+
className={`${BUTTON_CLASS} col-span-full`}
|
|
49
|
+
onClick={() => onSelect(lead.type)}
|
|
50
|
+
>
|
|
51
|
+
{lead.icon && (
|
|
52
|
+
<span className="flex h-5 w-5 shrink-0 items-center justify-center text-base-contrast-light/60">
|
|
53
|
+
{lead.icon}
|
|
54
|
+
</span>
|
|
55
|
+
)}
|
|
56
|
+
<span>{lead.label}</span>
|
|
57
|
+
</button>
|
|
58
|
+
<hr className="col-span-full border-base-200" />
|
|
59
|
+
</>
|
|
60
|
+
)}
|
|
61
|
+
{rest.map(({ type, label, icon }) => (
|
|
36
62
|
<button
|
|
37
63
|
key={type}
|
|
38
|
-
className=
|
|
64
|
+
className={BUTTON_CLASS}
|
|
39
65
|
onClick={() => onSelect(type)}
|
|
40
66
|
>
|
|
41
67
|
{icon && (
|
package/src/lib/container-ops.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { Section } from "../schemas/sections";
|
|
3
3
|
import { getAllSections } from "./registry";
|
|
4
|
+
import { sortInsertMenuOptions } from "./insert-menu";
|
|
4
5
|
|
|
5
6
|
interface ContainerContent {
|
|
6
7
|
columns: number;
|
|
@@ -40,9 +41,11 @@ export function reorderChild(container: Section, from: number, to: number): Sect
|
|
|
40
41
|
* single, testable source of truth (the container toolbar's "+" picker uses it).
|
|
41
42
|
*/
|
|
42
43
|
export function childInsertableTypes(): { type: string; label: string; icon: ReactNode }[] {
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
return sortInsertMenuOptions(
|
|
45
|
+
getAllSections()
|
|
46
|
+
.filter((d) => d.type !== "container" && d.type !== "spacer")
|
|
47
|
+
.map((d) => ({ type: d.type, label: d.label, icon: d.icon })),
|
|
48
|
+
);
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
export function insertChildAt(container: Section, child: Section, index: number): Section {
|
package/src/lib/insert-menu.ts
CHANGED
|
@@ -12,9 +12,43 @@ export interface InsertMenuOption {
|
|
|
12
12
|
icon?: ReactNode;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
/** Authored display order for the Add-section menu, most-reached-for first.
|
|
16
|
+
* `container` leads because the picker gives it its own full-width row above a
|
|
17
|
+
* divider. Anything absent — client-repo custom sections and their presets —
|
|
18
|
+
* sorts alphabetically after these. */
|
|
19
|
+
const INSERT_MENU_ORDER = [
|
|
20
|
+
"container",
|
|
21
|
+
"link_heading",
|
|
22
|
+
"sub_heading",
|
|
23
|
+
"sub_sub_heading",
|
|
24
|
+
"prose",
|
|
25
|
+
"media",
|
|
26
|
+
"video",
|
|
27
|
+
"button",
|
|
28
|
+
"colors",
|
|
29
|
+
"document",
|
|
30
|
+
"icon_list",
|
|
31
|
+
`${PRESET_PREFIX}do_dont_list`,
|
|
32
|
+
"dodont_media",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/** Sort insert-menu entries into `INSERT_MENU_ORDER`, with unlisted entries
|
|
36
|
+
* (custom sections) alphabetical by label at the end. Pure; returns a copy. */
|
|
37
|
+
export function sortInsertMenuOptions<T extends InsertMenuOption>(options: T[]): T[] {
|
|
38
|
+
return [...options].sort((a, b) => {
|
|
39
|
+
const ai = INSERT_MENU_ORDER.indexOf(a.type);
|
|
40
|
+
const bi = INSERT_MENU_ORDER.indexOf(b.type);
|
|
41
|
+
if (ai !== -1 && bi !== -1) return ai - bi;
|
|
42
|
+
if (ai !== -1) return -1;
|
|
43
|
+
if (bi !== -1) return 1;
|
|
44
|
+
return a.label.localeCompare(b.label);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Every non-spacer section type, plus presets contributed by registered
|
|
49
|
+
* sections, in `INSERT_MENU_ORDER`. Presets appear only when their owning
|
|
50
|
+
* section is registered, so a core-only build (no brand-guide sections) shows
|
|
51
|
+
* no brand-guide presets. */
|
|
18
52
|
export function buildInsertMenuOptions(sections: SectionDefinition[]): InsertMenuOption[] {
|
|
19
53
|
const typeOptions = sections
|
|
20
54
|
.filter((def) => def.type !== "spacer")
|
|
@@ -22,7 +56,7 @@ export function buildInsertMenuOptions(sections: SectionDefinition[]): InsertMen
|
|
|
22
56
|
const presetOptions = sections
|
|
23
57
|
.flatMap((def) => def.presets ?? [])
|
|
24
58
|
.map((p) => ({ type: `${PRESET_PREFIX}${p.id}`, label: p.label, icon: p.icon }));
|
|
25
|
-
return [...typeOptions, ...presetOptions];
|
|
59
|
+
return sortInsertMenuOptions([...typeOptions, ...presetOptions]);
|
|
26
60
|
}
|
|
27
61
|
|
|
28
62
|
/** Resolve an Add-menu selection to ready-to-insert content. `newId` mints ids for
|