@particle-academy/react-fancy 2.8.0 → 2.9.0
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/index.cjs +120 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -2
- package/dist/index.d.ts +27 -2
- package/dist/index.js +120 -79
- package/dist/index.js.map +1 -1
- package/docs/Canvas.md +5 -2
- package/docs/TreeNav.md +27 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -11462,12 +11462,30 @@ function countCardChildren(children) {
|
|
|
11462
11462
|
n += 1;
|
|
11463
11463
|
return;
|
|
11464
11464
|
}
|
|
11465
|
-
|
|
11466
|
-
|
|
11467
|
-
}
|
|
11465
|
+
const inner = child.props.children;
|
|
11466
|
+
if (inner !== void 0) n += countCardChildren(inner);
|
|
11468
11467
|
});
|
|
11469
11468
|
return n;
|
|
11470
11469
|
}
|
|
11470
|
+
function findCardIndex(children, cardId) {
|
|
11471
|
+
let idx = -1;
|
|
11472
|
+
let i = 0;
|
|
11473
|
+
function walk2(nodes) {
|
|
11474
|
+
react.Children.forEach(nodes, (child) => {
|
|
11475
|
+
if (idx !== -1) return;
|
|
11476
|
+
if (!react.isValidElement(child)) return;
|
|
11477
|
+
if (child.type === KanbanCard) {
|
|
11478
|
+
if (child.props.id === cardId) idx = i;
|
|
11479
|
+
i += 1;
|
|
11480
|
+
return;
|
|
11481
|
+
}
|
|
11482
|
+
const inner = child.props.children;
|
|
11483
|
+
if (inner !== void 0) walk2(inner);
|
|
11484
|
+
});
|
|
11485
|
+
}
|
|
11486
|
+
walk2(children);
|
|
11487
|
+
return idx;
|
|
11488
|
+
}
|
|
11471
11489
|
function KanbanColumn({
|
|
11472
11490
|
children,
|
|
11473
11491
|
id,
|
|
@@ -11480,26 +11498,46 @@ function KanbanColumn({
|
|
|
11480
11498
|
const { onCardMove, draggedCard, dragSource, registerColumn } = useKanban();
|
|
11481
11499
|
const [dragOver, setDragOver] = react.useState(false);
|
|
11482
11500
|
const [dropIndex, setDropIndex] = react.useState(null);
|
|
11501
|
+
const [dropY, setDropY] = react.useState(null);
|
|
11483
11502
|
const cardsRef = react.useRef(null);
|
|
11484
11503
|
react.useEffect(() => registerColumn(id), [id, registerColumn]);
|
|
11485
|
-
const
|
|
11504
|
+
const updateDrop = react.useCallback((clientY) => {
|
|
11486
11505
|
const container = cardsRef.current;
|
|
11487
11506
|
if (!container) {
|
|
11488
11507
|
setDropIndex(null);
|
|
11508
|
+
setDropY(null);
|
|
11489
11509
|
return;
|
|
11490
11510
|
}
|
|
11491
|
-
const
|
|
11492
|
-
|
|
11511
|
+
const all = Array.from(
|
|
11512
|
+
container.querySelectorAll(
|
|
11513
|
+
"[data-react-fancy-kanban-card]"
|
|
11514
|
+
)
|
|
11515
|
+
);
|
|
11516
|
+
const cards = all.filter(
|
|
11517
|
+
(el) => el.closest("[data-react-fancy-kanban-column]") === cardsRef.current?.closest("[data-react-fancy-kanban-column]")
|
|
11493
11518
|
);
|
|
11519
|
+
const containerRect = container.getBoundingClientRect();
|
|
11520
|
+
if (cards.length === 0) {
|
|
11521
|
+
setDropIndex(0);
|
|
11522
|
+
setDropY(0);
|
|
11523
|
+
return;
|
|
11524
|
+
}
|
|
11494
11525
|
let idx = cards.length;
|
|
11526
|
+
let yRel = 0;
|
|
11495
11527
|
for (let i = 0; i < cards.length; i++) {
|
|
11496
11528
|
const rect = cards[i].getBoundingClientRect();
|
|
11497
11529
|
if (clientY < rect.top + rect.height / 2) {
|
|
11498
11530
|
idx = i;
|
|
11531
|
+
yRel = rect.top - containerRect.top;
|
|
11499
11532
|
break;
|
|
11500
11533
|
}
|
|
11501
11534
|
}
|
|
11535
|
+
if (idx === cards.length) {
|
|
11536
|
+
const last = cards[cards.length - 1].getBoundingClientRect();
|
|
11537
|
+
yRel = last.bottom - containerRect.top;
|
|
11538
|
+
}
|
|
11502
11539
|
setDropIndex(idx);
|
|
11540
|
+
setDropY(yRel);
|
|
11503
11541
|
}, []);
|
|
11504
11542
|
const handleDragOver = react.useCallback(
|
|
11505
11543
|
(e) => {
|
|
@@ -11507,14 +11545,15 @@ function KanbanColumn({
|
|
|
11507
11545
|
e.preventDefault();
|
|
11508
11546
|
e.stopPropagation();
|
|
11509
11547
|
setDragOver(true);
|
|
11510
|
-
|
|
11548
|
+
updateDrop(e.clientY);
|
|
11511
11549
|
},
|
|
11512
|
-
[draggedCard,
|
|
11550
|
+
[draggedCard, updateDrop]
|
|
11513
11551
|
);
|
|
11514
11552
|
const handleDragLeave = react.useCallback((e) => {
|
|
11515
11553
|
if (e.currentTarget.contains(e.relatedTarget)) return;
|
|
11516
11554
|
setDragOver(false);
|
|
11517
11555
|
setDropIndex(null);
|
|
11556
|
+
setDropY(null);
|
|
11518
11557
|
}, []);
|
|
11519
11558
|
const handleDrop = react.useCallback(
|
|
11520
11559
|
(e) => {
|
|
@@ -11522,16 +11561,15 @@ function KanbanColumn({
|
|
|
11522
11561
|
e.preventDefault();
|
|
11523
11562
|
e.stopPropagation();
|
|
11524
11563
|
const target = dropIndex ?? 0;
|
|
11525
|
-
if (dragSource
|
|
11564
|
+
if (dragSource) {
|
|
11526
11565
|
let finalIdx = target;
|
|
11527
11566
|
if (dragSource === id) {
|
|
11528
11567
|
const srcIdx = findCardIndex(children, draggedCard);
|
|
11529
|
-
if (srcIdx !== -1 && target > srcIdx)
|
|
11530
|
-
finalIdx = target - 1;
|
|
11531
|
-
}
|
|
11568
|
+
if (srcIdx !== -1 && target > srcIdx) finalIdx = target - 1;
|
|
11532
11569
|
if (srcIdx === finalIdx) {
|
|
11533
11570
|
setDragOver(false);
|
|
11534
11571
|
setDropIndex(null);
|
|
11572
|
+
setDropY(null);
|
|
11535
11573
|
return;
|
|
11536
11574
|
}
|
|
11537
11575
|
}
|
|
@@ -11539,27 +11577,13 @@ function KanbanColumn({
|
|
|
11539
11577
|
}
|
|
11540
11578
|
setDragOver(false);
|
|
11541
11579
|
setDropIndex(null);
|
|
11580
|
+
setDropY(null);
|
|
11542
11581
|
},
|
|
11543
11582
|
[draggedCard, dragSource, dropIndex, id, onCardMove, children]
|
|
11544
11583
|
);
|
|
11545
11584
|
const cardCount = countCardChildren(children);
|
|
11546
|
-
if (hideWhenEmpty && cardCount === 0 && !draggedCard)
|
|
11547
|
-
|
|
11548
|
-
}
|
|
11549
|
-
let cardSeen = 0;
|
|
11550
|
-
const showIndicator = draggedCard !== null && dropIndex !== null && dragOver;
|
|
11551
|
-
const renderedChildren = react.Children.toArray(children).map((child, i) => {
|
|
11552
|
-
const isCard = react.isValidElement(child) && child.type === KanbanCard;
|
|
11553
|
-
const indicator = showIndicator && isCard && cardSeen === dropIndex ? /* @__PURE__ */ jsxRuntime.jsx(DropIndicator, {}, `drop-${i}`) : null;
|
|
11554
|
-
if (isCard) cardSeen += 1;
|
|
11555
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
|
|
11556
|
-
indicator,
|
|
11557
|
-
child
|
|
11558
|
-
] }, i);
|
|
11559
|
-
});
|
|
11560
|
-
if (showIndicator && dropIndex === cardCount) {
|
|
11561
|
-
renderedChildren.push(/* @__PURE__ */ jsxRuntime.jsx(DropIndicator, {}, "drop-end"));
|
|
11562
|
-
}
|
|
11585
|
+
if (hideWhenEmpty && cardCount === 0 && !draggedCard) return null;
|
|
11586
|
+
const showIndicator = draggedCard !== null && dropIndex !== null && dropY !== null && dragOver;
|
|
11563
11587
|
const overWip = wipLimit !== void 0 && cardCount > wipLimit;
|
|
11564
11588
|
return /* @__PURE__ */ jsxRuntime.jsx(KanbanColumnContext.Provider, { value: id, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
11565
11589
|
"div",
|
|
@@ -11594,36 +11618,22 @@ function KanbanColumn({
|
|
|
11594
11618
|
}
|
|
11595
11619
|
)
|
|
11596
11620
|
] }),
|
|
11597
|
-
/* @__PURE__ */ jsxRuntime.
|
|
11621
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { ref: cardsRef, className: "relative flex flex-1 flex-col gap-2", children: [
|
|
11622
|
+
children,
|
|
11623
|
+
showIndicator && /* @__PURE__ */ jsxRuntime.jsx(
|
|
11624
|
+
"div",
|
|
11625
|
+
{
|
|
11626
|
+
"data-react-fancy-kanban-drop-indicator": "",
|
|
11627
|
+
style: { top: dropY },
|
|
11628
|
+
className: "pointer-events-none absolute left-0 right-0 h-0.5 -translate-y-1/2 rounded-full bg-blue-500/80 shadow-[0_0_0_3px_rgba(59,130,246,0.15)]"
|
|
11629
|
+
}
|
|
11630
|
+
)
|
|
11631
|
+
] })
|
|
11598
11632
|
]
|
|
11599
11633
|
}
|
|
11600
11634
|
) });
|
|
11601
11635
|
}
|
|
11602
11636
|
KanbanColumn.displayName = "KanbanColumn";
|
|
11603
|
-
function DropIndicator() {
|
|
11604
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
11605
|
-
"div",
|
|
11606
|
-
{
|
|
11607
|
-
"data-react-fancy-kanban-drop-indicator": "",
|
|
11608
|
-
className: "h-0.5 -my-1 rounded-full bg-blue-500/80"
|
|
11609
|
-
}
|
|
11610
|
-
);
|
|
11611
|
-
}
|
|
11612
|
-
function findCardIndex(children, cardId) {
|
|
11613
|
-
let idx = -1;
|
|
11614
|
-
let i = 0;
|
|
11615
|
-
react.Children.forEach(children, (child) => {
|
|
11616
|
-
if (idx !== -1) return;
|
|
11617
|
-
if (!react.isValidElement(child)) return;
|
|
11618
|
-
if (child.type === KanbanCard) {
|
|
11619
|
-
if (child.props.id === cardId) {
|
|
11620
|
-
idx = i;
|
|
11621
|
-
}
|
|
11622
|
-
i += 1;
|
|
11623
|
-
}
|
|
11624
|
-
});
|
|
11625
|
-
return idx;
|
|
11626
|
-
}
|
|
11627
11637
|
function KanbanColumnHandle({
|
|
11628
11638
|
children,
|
|
11629
11639
|
className
|
|
@@ -11761,7 +11771,7 @@ function useCanvas() {
|
|
|
11761
11771
|
return ctx;
|
|
11762
11772
|
}
|
|
11763
11773
|
function CanvasNode({ children, id, x, y, draggable, onPositionChange, className, style }) {
|
|
11764
|
-
const { registerNode, unregisterNode, viewport } = useCanvas();
|
|
11774
|
+
const { registerNode, unregisterNode, viewport, gridSize, snapToGrid } = useCanvas();
|
|
11765
11775
|
const nodeRef = react.useRef(null);
|
|
11766
11776
|
const isDragging = react.useRef(false);
|
|
11767
11777
|
const dragStart = react.useRef({ mouseX: 0, mouseY: 0, nodeX: 0, nodeY: 0 });
|
|
@@ -11794,9 +11804,15 @@ function CanvasNode({ children, id, x, y, draggable, onPositionChange, className
|
|
|
11794
11804
|
if (!isDragging.current) return;
|
|
11795
11805
|
const dx = (e.clientX - dragStart.current.mouseX) / viewport.zoom;
|
|
11796
11806
|
const dy = (e.clientY - dragStart.current.mouseY) / viewport.zoom;
|
|
11797
|
-
|
|
11807
|
+
let nx = dragStart.current.nodeX + dx;
|
|
11808
|
+
let ny = dragStart.current.nodeY + dy;
|
|
11809
|
+
if (snapToGrid && gridSize > 0) {
|
|
11810
|
+
nx = Math.round(nx / gridSize) * gridSize;
|
|
11811
|
+
ny = Math.round(ny / gridSize) * gridSize;
|
|
11812
|
+
}
|
|
11813
|
+
onPositionChange?.(nx, ny);
|
|
11798
11814
|
},
|
|
11799
|
-
[viewport.zoom, onPositionChange]
|
|
11815
|
+
[viewport.zoom, onPositionChange, snapToGrid, gridSize]
|
|
11800
11816
|
);
|
|
11801
11817
|
const handlePointerUp = react.useCallback(() => {
|
|
11802
11818
|
isDragging.current = false;
|
|
@@ -12044,6 +12060,10 @@ function CanvasRoot({
|
|
|
12044
12060
|
pannable = true,
|
|
12045
12061
|
zoomable = true,
|
|
12046
12062
|
showGrid = false,
|
|
12063
|
+
gridStyle = "dots",
|
|
12064
|
+
gridSize = 20,
|
|
12065
|
+
gridColor = "rgb(161 161 170 / 0.3)",
|
|
12066
|
+
snapToGrid = false,
|
|
12047
12067
|
fitOnMount = false,
|
|
12048
12068
|
className,
|
|
12049
12069
|
style
|
|
@@ -12061,8 +12081,8 @@ function CanvasRoot({
|
|
|
12061
12081
|
containerRef
|
|
12062
12082
|
});
|
|
12063
12083
|
const ctx = react.useMemo(
|
|
12064
|
-
() => ({ viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, containerRef }),
|
|
12065
|
-
[viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion]
|
|
12084
|
+
() => ({ viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, containerRef, gridSize, snapToGrid }),
|
|
12085
|
+
[viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, gridSize, snapToGrid]
|
|
12066
12086
|
);
|
|
12067
12087
|
const hasFitted = react.useRef(false);
|
|
12068
12088
|
react.useEffect(() => {
|
|
@@ -12118,9 +12138,13 @@ function CanvasRoot({
|
|
|
12118
12138
|
{
|
|
12119
12139
|
"data-canvas-bg": "",
|
|
12120
12140
|
className: "absolute inset-0",
|
|
12121
|
-
style: showGrid ? {
|
|
12122
|
-
backgroundImage: `
|
|
12123
|
-
backgroundSize: `${
|
|
12141
|
+
style: showGrid && gridStyle !== "none" ? gridStyle === "lines" ? {
|
|
12142
|
+
backgroundImage: `linear-gradient(to right, ${gridColor} 1px, transparent 1px), linear-gradient(to bottom, ${gridColor} 1px, transparent 1px)`,
|
|
12143
|
+
backgroundSize: `${gridSize * viewport.zoom}px ${gridSize * viewport.zoom}px`,
|
|
12144
|
+
backgroundPosition: `${viewport.panX}px ${viewport.panY}px`
|
|
12145
|
+
} : {
|
|
12146
|
+
backgroundImage: `radial-gradient(circle, ${gridColor} 1px, transparent 1px)`,
|
|
12147
|
+
backgroundSize: `${gridSize * viewport.zoom}px ${gridSize * viewport.zoom}px`,
|
|
12124
12148
|
backgroundPosition: `${viewport.panX}px ${viewport.panY}px`
|
|
12125
12149
|
} : void 0
|
|
12126
12150
|
}
|
|
@@ -12837,6 +12861,8 @@ function TreeNode({ node, depth }) {
|
|
|
12837
12861
|
dragState,
|
|
12838
12862
|
setDragState,
|
|
12839
12863
|
onNodeMove,
|
|
12864
|
+
acceptExternalDrops,
|
|
12865
|
+
onExternalDrop,
|
|
12840
12866
|
nodes,
|
|
12841
12867
|
expandNode
|
|
12842
12868
|
} = useTreeNav();
|
|
@@ -12879,14 +12905,18 @@ function TreeNode({ node, depth }) {
|
|
|
12879
12905
|
clearAutoExpand();
|
|
12880
12906
|
setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });
|
|
12881
12907
|
}, [clearAutoExpand, setDragState]);
|
|
12908
|
+
const isExternalDrag = !dragState.draggedNodeId;
|
|
12882
12909
|
const handleDragOver = react.useCallback((e) => {
|
|
12883
|
-
if (
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
|
|
12910
|
+
if (isExternalDrag) {
|
|
12911
|
+
if (!acceptExternalDrops) return;
|
|
12912
|
+
} else {
|
|
12913
|
+
const sourceId = dragState.draggedNodeId;
|
|
12914
|
+
if (sourceId === node.id) return;
|
|
12915
|
+
if (isDescendantOf(nodes, sourceId, node.id)) return;
|
|
12916
|
+
}
|
|
12887
12917
|
e.preventDefault();
|
|
12888
12918
|
e.stopPropagation();
|
|
12889
|
-
e.dataTransfer.dropEffect = "move";
|
|
12919
|
+
e.dataTransfer.dropEffect = isExternalDrag ? "copy" : "move";
|
|
12890
12920
|
const position = computeDropPosition(e, !!isFolder);
|
|
12891
12921
|
if (isFolder && !isExpanded && position === "inside") {
|
|
12892
12922
|
if (!autoExpandTimer.current) {
|
|
@@ -12899,9 +12929,9 @@ function TreeNode({ node, depth }) {
|
|
|
12899
12929
|
clearAutoExpand();
|
|
12900
12930
|
}
|
|
12901
12931
|
if (dragState.dropTargetId !== node.id || dragState.dropPosition !== position) {
|
|
12902
|
-
setDragState({ draggedNodeId:
|
|
12932
|
+
setDragState({ draggedNodeId: dragState.draggedNodeId, dropTargetId: node.id, dropPosition: position });
|
|
12903
12933
|
}
|
|
12904
|
-
}, [dragState, node.id, isFolder, isExpanded, nodes, setDragState, expandNode, clearAutoExpand]);
|
|
12934
|
+
}, [dragState, isExternalDrag, acceptExternalDrops, node.id, isFolder, isExpanded, nodes, setDragState, expandNode, clearAutoExpand]);
|
|
12905
12935
|
const handleDragLeave = react.useCallback((e) => {
|
|
12906
12936
|
if (!e.currentTarget.contains(e.relatedTarget)) {
|
|
12907
12937
|
clearAutoExpand();
|
|
@@ -12915,21 +12945,25 @@ function TreeNode({ node, depth }) {
|
|
|
12915
12945
|
e.stopPropagation();
|
|
12916
12946
|
clearAutoExpand();
|
|
12917
12947
|
const sourceId = dragState.draggedNodeId;
|
|
12918
|
-
const position = dragState.dropPosition;
|
|
12919
|
-
if (
|
|
12920
|
-
|
|
12921
|
-
|
|
12922
|
-
|
|
12948
|
+
const position = dragState.dropPosition ?? computeDropPosition(e, !!isFolder);
|
|
12949
|
+
if (sourceId) {
|
|
12950
|
+
if (sourceId === node.id) return;
|
|
12951
|
+
if (isDescendantOf(nodes, sourceId, node.id)) return;
|
|
12952
|
+
onNodeMove?.(sourceId, node.id, position);
|
|
12953
|
+
} else if (acceptExternalDrops) {
|
|
12954
|
+
onExternalDrop?.(e, node, position);
|
|
12955
|
+
}
|
|
12923
12956
|
setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });
|
|
12924
|
-
}, [dragState, node
|
|
12957
|
+
}, [dragState, node, isFolder, nodes, onNodeMove, acceptExternalDrops, onExternalDrop, setDragState, clearAutoExpand]);
|
|
12925
12958
|
const canDrag = draggable && !node.disabled;
|
|
12959
|
+
const dropEnabled = draggable || acceptExternalDrops;
|
|
12926
12960
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
12927
12961
|
"div",
|
|
12928
12962
|
{
|
|
12929
12963
|
"data-react-fancy-tree-node": "",
|
|
12930
|
-
onDragOver:
|
|
12931
|
-
onDragLeave:
|
|
12932
|
-
onDrop:
|
|
12964
|
+
onDragOver: dropEnabled ? handleDragOver : void 0,
|
|
12965
|
+
onDragLeave: dropEnabled ? handleDragLeave : void 0,
|
|
12966
|
+
onDrop: dropEnabled ? handleDrop : void 0,
|
|
12933
12967
|
children: [
|
|
12934
12968
|
isDropTarget && dropPosition === "before" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
12935
12969
|
"div",
|
|
@@ -13002,6 +13036,8 @@ function TreeNavRoot({
|
|
|
13002
13036
|
onNodeContextMenu,
|
|
13003
13037
|
draggable = false,
|
|
13004
13038
|
onNodeMove,
|
|
13039
|
+
acceptExternalDrops = false,
|
|
13040
|
+
onExternalDrop,
|
|
13005
13041
|
expandedIds: controlledExpanded,
|
|
13006
13042
|
defaultExpandedIds,
|
|
13007
13043
|
onExpandedChange,
|
|
@@ -13055,6 +13091,8 @@ function TreeNavRoot({
|
|
|
13055
13091
|
dragState,
|
|
13056
13092
|
setDragState,
|
|
13057
13093
|
onNodeMove,
|
|
13094
|
+
acceptExternalDrops,
|
|
13095
|
+
onExternalDrop,
|
|
13058
13096
|
nodes,
|
|
13059
13097
|
expandNode
|
|
13060
13098
|
}),
|
|
@@ -13069,16 +13107,19 @@ function TreeNavRoot({
|
|
|
13069
13107
|
draggable,
|
|
13070
13108
|
dragState,
|
|
13071
13109
|
onNodeMove,
|
|
13110
|
+
acceptExternalDrops,
|
|
13111
|
+
onExternalDrop,
|
|
13072
13112
|
nodes,
|
|
13073
13113
|
expandNode
|
|
13074
13114
|
]
|
|
13075
13115
|
);
|
|
13116
|
+
const dropEnabled = draggable || acceptExternalDrops;
|
|
13076
13117
|
return /* @__PURE__ */ jsxRuntime.jsx(TreeNavContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
13077
13118
|
"nav",
|
|
13078
13119
|
{
|
|
13079
13120
|
"data-react-fancy-tree-nav": "",
|
|
13080
13121
|
className: cn("flex flex-col gap-0.5 py-1 text-sm", className),
|
|
13081
|
-
onDragEnd:
|
|
13122
|
+
onDragEnd: dropEnabled ? handleDragEnd : void 0,
|
|
13082
13123
|
children: nodes.map((node) => /* @__PURE__ */ jsxRuntime.jsx(TreeNode, { node, depth: 0 }, node.id))
|
|
13083
13124
|
}
|
|
13084
13125
|
) });
|