@evergis/react 4.0.143 → 4.0.145
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/components/Dashboard/components/ResizeHandle/constants.d.ts +15 -0
- package/dist/components/Dashboard/components/ResizeHandle/index.d.ts +3 -0
- package/dist/components/Dashboard/components/ResizeHandle/styled.d.ts +17 -0
- package/dist/components/Dashboard/components/ResizeHandle/types.d.ts +2 -0
- package/dist/components/Dashboard/components/index.d.ts +1 -0
- package/dist/components/Dashboard/containers/StructuredDataContainer/types.d.ts +12 -1
- package/dist/components/Dashboard/elements/ElementTable/components/AttachmentsCell.d.ts +9 -0
- package/dist/components/Dashboard/elements/ElementTable/components/TableColumnResizer.d.ts +9 -0
- package/dist/components/Dashboard/elements/ElementTable/components/TableHeadRow.d.ts +9 -0
- package/dist/components/Dashboard/elements/ElementTable/constants.d.ts +2 -0
- package/dist/components/Dashboard/elements/ElementTable/hooks/useAttachmentsCell.d.ts +26 -0
- package/dist/components/Dashboard/elements/ElementTable/hooks/useColumnResize.d.ts +19 -0
- package/dist/components/Dashboard/elements/ElementTable/hooks/useTableView.d.ts +5 -0
- package/dist/components/Dashboard/elements/ElementTable/styled.d.ts +31 -3
- package/dist/components/Dashboard/elements/ElementTable/types.d.ts +16 -0
- package/dist/components/Dashboard/elements/ElementTable/utils/columnStyle.d.ts +19 -0
- package/dist/components/Dashboard/grid/constants.d.ts +0 -14
- package/dist/components/Dashboard/grid/hooks/useGridHeightResize.d.ts +2 -2
- package/dist/components/Dashboard/grid/hooks/useGridResize.d.ts +6 -10
- package/dist/components/Dashboard/hooks/index.d.ts +1 -0
- package/dist/components/Dashboard/hooks/useResizeDrag.d.ts +29 -0
- package/dist/components/Dashboard/types.d.ts +19 -0
- package/dist/index.js +409 -145
- package/dist/index.js.map +1 -1
- package/dist/react.esm.js +408 -145
- package/dist/react.esm.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -8170,6 +8170,47 @@ const useProjectDashboardInit = () => {
|
|
|
8170
8170
|
}, [projectInfo, updateProject]);
|
|
8171
8171
|
};
|
|
8172
8172
|
|
|
8173
|
+
/**
|
|
8174
|
+
* Перетаскивание границы: подписка на ручку, снимок на старте, предпросмотр на движении,
|
|
8175
|
+
* запись результата на отпускании.
|
|
8176
|
+
*
|
|
8177
|
+
* Общий для сетки дашборда и колонок таблицы. Различаются они только тем, что именно меряют и
|
|
8178
|
+
* куда пишут; сам жест обязан ощущаться одинаково — включая то, что мышь ведут по документу, а
|
|
8179
|
+
* не по самой ручке, и отпускание за пределами окна тоже завершает жест.
|
|
8180
|
+
*/
|
|
8181
|
+
const useResizeDrag = ({ onStart, onMove, onEnd }) => {
|
|
8182
|
+
const [handle, setHandle] = React.useState(null);
|
|
8183
|
+
const [dragging, setDragging] = React.useState(false);
|
|
8184
|
+
const stateRef = React.useRef(null);
|
|
8185
|
+
const mouseDown = React.useCallback((event) => {
|
|
8186
|
+
const state = onStart(event);
|
|
8187
|
+
if (state === null) {
|
|
8188
|
+
return;
|
|
8189
|
+
}
|
|
8190
|
+
// Гасим выделение текста: жест ведут мышью по документу, и без этого он выделял бы всё,
|
|
8191
|
+
// над чем проходит курсор.
|
|
8192
|
+
event.preventDefault();
|
|
8193
|
+
stateRef.current = state;
|
|
8194
|
+
setDragging(true);
|
|
8195
|
+
}, [onStart]);
|
|
8196
|
+
const mouseMove = React.useCallback((event) => {
|
|
8197
|
+
if (stateRef.current !== null) {
|
|
8198
|
+
onMove(stateRef.current, event);
|
|
8199
|
+
}
|
|
8200
|
+
}, [onMove]);
|
|
8201
|
+
const mouseUp = React.useCallback((event) => {
|
|
8202
|
+
const state = stateRef.current;
|
|
8203
|
+
if (state === null) {
|
|
8204
|
+
return;
|
|
8205
|
+
}
|
|
8206
|
+
stateRef.current = null;
|
|
8207
|
+
setDragging(false);
|
|
8208
|
+
onEnd(state, event);
|
|
8209
|
+
}, [onEnd]);
|
|
8210
|
+
uilibGl.useDragAndDropEffect(handle, { mouseDown, mouseMove, mouseUp });
|
|
8211
|
+
return { setHandle, dragging };
|
|
8212
|
+
};
|
|
8213
|
+
|
|
8173
8214
|
const useRelatedDataSourceAttributes = ({ type = exports.WidgetType.Dashboard, elementConfig, dataSources, feature, }) => {
|
|
8174
8215
|
const { layerInfos } = useWidgetContext(type);
|
|
8175
8216
|
const { currentPage } = useWidgetPage(type);
|
|
@@ -9604,6 +9645,92 @@ const FiltersContainer = React.memo(({ elementConfig, config, type, renderElemen
|
|
|
9604
9645
|
return (jsxRuntime.jsxs(ContainerRoot, { ...root, children: [jsxRuntime.jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsxRuntime.jsx(uilibGl.Flex, { mb: !isVisible && selectedItems.length ? "2rem" : 0, children: jsxRuntime.jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }) }), isLoading && jsxRuntime.jsx(ContainerLoading, {}), !isLoading && isVisible && (jsxRuntime.jsx(FiltersContainerWrapper, { ...body, "$padding": padding, "$bgColor": bgColor, "$fontSize": fontSize, "$fontColor": fontColor, children: filterItems?.map(renderFilter) })), jsxRuntime.jsx(HiddenTitleItems, { elementConfig: elementConfig, config: config, type: type, filter: filterItems[0]?.options?.filterName })] }));
|
|
9605
9646
|
});
|
|
9606
9647
|
|
|
9648
|
+
/**
|
|
9649
|
+
* Заход зоны захвата на содержимое с каждой стороны границы, px.
|
|
9650
|
+
*
|
|
9651
|
+
* Без него попасть курсором ровно в границу невозможно: у таблицы её толщина — один пиксель,
|
|
9652
|
+
* а у сетки зазора может не быть вовсе. Ручка абсолютная и в раскладке не участвует, поэтому
|
|
9653
|
+
* расширение ничего не сдвигает.
|
|
9654
|
+
*/
|
|
9655
|
+
const HANDLE_BLEED_PX = 3;
|
|
9656
|
+
/**
|
|
9657
|
+
* Маркер ручки: нажатие на ней тянет границу, а не ячейку сетки.
|
|
9658
|
+
*
|
|
9659
|
+
* Ставит его сам компонент. Ручка колонки таблицы, оказавшейся внутри ячейки сетки, обязана
|
|
9660
|
+
* помечаться так же — иначе нажатие на ней начинало бы перетаскивание всей ячейки.
|
|
9661
|
+
*/
|
|
9662
|
+
const RESIZE_HANDLE_ATTR = "data-grid-handle";
|
|
9663
|
+
|
|
9664
|
+
/**
|
|
9665
|
+
* Зона захвата = зазор плюс небольшой заход на содержимое с обеих сторон.
|
|
9666
|
+
*
|
|
9667
|
+
* Смещение уводит дальний край ручки за зазор ровно на `HANDLE_BLEED_PX`, поэтому вместе с
|
|
9668
|
+
* шириной она оказывается симметрично «надета» на границу: от `-bleed` до `gap + bleed`.
|
|
9669
|
+
*/
|
|
9670
|
+
const handleSize = `calc(var(--grid-gap, 0px) + ${HANDLE_BLEED_PX * 2}px)`;
|
|
9671
|
+
const handleOffset = `calc(-1 * (var(--grid-gap, 0px) + ${HANDLE_BLEED_PX}px))`;
|
|
9672
|
+
/**
|
|
9673
|
+
* Ручка перетаскивания границы. Общая для сетки дашборда и колонок таблицы: жест один и тот же,
|
|
9674
|
+
* и выглядеть он обязан одинаково.
|
|
9675
|
+
*
|
|
9676
|
+
* Абсолютная и целиком лежит в зазоре, поэтому включение режима редактирования не сдвигает
|
|
9677
|
+
* раскладку ни на пиксель. Сам ползунок (`::after`) появляется только при наведении и во время
|
|
9678
|
+
* перетаскивания — в покое раскладка выглядит как обычно.
|
|
9679
|
+
*
|
|
9680
|
+
* Ширину зазора берёт из переменной: у сетки её объявляет тело сетки, у таблицы зазора нет вовсе,
|
|
9681
|
+
* и зона захвата сжимается до одного захода на содержимое с каждой стороны.
|
|
9682
|
+
*/
|
|
9683
|
+
const ResizeHandle = styled.div.attrs({ [RESIZE_HANDLE_ATTR]: true }).withConfig({ displayName: "ResizeHandle", componentId: "sc-1n6ja5b" }) `
|
|
9684
|
+
position: absolute;
|
|
9685
|
+
z-index: 3;
|
|
9686
|
+
touch-action: none;
|
|
9687
|
+
|
|
9688
|
+
::after {
|
|
9689
|
+
content: "";
|
|
9690
|
+
position: absolute;
|
|
9691
|
+
border-radius: 0.0625rem;
|
|
9692
|
+
background: transparent;
|
|
9693
|
+
transition: background-color 120ms ease;
|
|
9694
|
+
}
|
|
9695
|
+
|
|
9696
|
+
:hover::after,
|
|
9697
|
+
&[data-dragging="true"]::after {
|
|
9698
|
+
background: ${({ theme: { palette } }) => palette.primary};
|
|
9699
|
+
}
|
|
9700
|
+
|
|
9701
|
+
${({ $axis }) => $axis === "column"
|
|
9702
|
+
? styled.css `
|
|
9703
|
+
top: 0;
|
|
9704
|
+
bottom: 0;
|
|
9705
|
+
right: ${handleOffset};
|
|
9706
|
+
width: ${handleSize};
|
|
9707
|
+
cursor: col-resize;
|
|
9708
|
+
|
|
9709
|
+
::after {
|
|
9710
|
+
top: 20%;
|
|
9711
|
+
bottom: 20%;
|
|
9712
|
+
left: 50%;
|
|
9713
|
+
width: 0.125rem;
|
|
9714
|
+
transform: translateX(-50%);
|
|
9715
|
+
}
|
|
9716
|
+
`
|
|
9717
|
+
: styled.css `
|
|
9718
|
+
left: 0;
|
|
9719
|
+
right: 0;
|
|
9720
|
+
bottom: ${handleOffset};
|
|
9721
|
+
height: ${handleSize};
|
|
9722
|
+
cursor: row-resize;
|
|
9723
|
+
|
|
9724
|
+
::after {
|
|
9725
|
+
left: 20%;
|
|
9726
|
+
right: 20%;
|
|
9727
|
+
top: 50%;
|
|
9728
|
+
height: 0.125rem;
|
|
9729
|
+
transform: translateY(-50%);
|
|
9730
|
+
}
|
|
9731
|
+
`};
|
|
9732
|
+
`;
|
|
9733
|
+
|
|
9607
9734
|
/** Размер, при котором контейнер занимает всю ячейку родителя. */
|
|
9608
9735
|
const FILL_SIZE = "100%";
|
|
9609
9736
|
/** Собственные горизонтальные дефолты обёртки, мешающие контейнеру занять всю ширину ячейки. */
|
|
@@ -9683,6 +9810,7 @@ const getWrapperSizeStyle = ({ style, width: widthOption, height: heightOption,
|
|
|
9683
9810
|
};
|
|
9684
9811
|
};
|
|
9685
9812
|
|
|
9813
|
+
// Прямой импорт листового модуля, а не барреля components: тот тянет за собой контейнеры.
|
|
9686
9814
|
/**
|
|
9687
9815
|
* Минимальный размер трека, px. Ниже него ресайз не пускает — иначе ячейку не за что схватить.
|
|
9688
9816
|
*
|
|
@@ -9718,14 +9846,6 @@ const GRID_AUTO_FILL_DEFAULTS = { ...CONTAINERS_GROUP_DEFAULTS, minHeight: FILL_
|
|
|
9718
9846
|
* раскладки, поэтому задаётся явно через `options.gap`, а не навязывается всем сеткам.
|
|
9719
9847
|
*/
|
|
9720
9848
|
const DEFAULT_GRID_GAP = 0;
|
|
9721
|
-
/**
|
|
9722
|
-
* Насколько ручка ресайза заходит на содержимое с каждой стороны, px.
|
|
9723
|
-
*
|
|
9724
|
-
* По умолчанию зазора нет вовсе, так что попасть курсором ровно в границу невозможно — зона
|
|
9725
|
-
* захвата всегда шире зазора на эту величину с каждой стороны. Ручка абсолютная и в раскладке
|
|
9726
|
-
* не участвует, поэтому расширение ничего не сдвигает.
|
|
9727
|
-
*/
|
|
9728
|
-
const HANDLE_BLEED_PX = 3;
|
|
9729
9849
|
/**
|
|
9730
9850
|
* Сдвиг курсора, после которого нажатие на ячейке считается перетаскиванием, px.
|
|
9731
9851
|
*
|
|
@@ -9735,17 +9855,13 @@ const HANDLE_BLEED_PX = 3;
|
|
|
9735
9855
|
const DRAG_THRESHOLD_PX = 4;
|
|
9736
9856
|
/** Маркер ячейки в DOM: по нему ищется цель перетаскивания под курсором. */
|
|
9737
9857
|
const GRID_CELL_ATTR = "data-grid-cell";
|
|
9738
|
-
/** Маркер ручки ресайза: нажатие на ней тянет границу, а не ячейку. */
|
|
9739
|
-
const GRID_HANDLE_ATTR = "data-grid-handle";
|
|
9740
|
-
/** Тот же маркер пропсами: имя атрибута вычисляемое, а в JSX такое подставляется только спредом. */
|
|
9741
|
-
const GRID_HANDLE_PROPS = { [GRID_HANDLE_ATTR]: true };
|
|
9742
9858
|
/**
|
|
9743
9859
|
* Нажатия, которые перетаскиванием ячейки не считаются.
|
|
9744
9860
|
*
|
|
9745
9861
|
* Ручка тянет границу трека, а поля ввода живут своей жизнью: там нажатие ставит каретку и
|
|
9746
9862
|
* выделяет текст, и подменять это перестановкой ячеек нельзя.
|
|
9747
9863
|
*/
|
|
9748
|
-
const NO_CELL_DRAG_SELECTOR = `[${
|
|
9864
|
+
const NO_CELL_DRAG_SELECTOR = `[${RESIZE_HANDLE_ATTR}], input, textarea, select, [contenteditable="true"]`;
|
|
9749
9865
|
/**
|
|
9750
9866
|
* Разметка идущего жеста: ячейка-источник, цель под курсором и сам факт перетаскивания.
|
|
9751
9867
|
*
|
|
@@ -9766,72 +9882,6 @@ const FR_PRECISION = 1000;
|
|
|
9766
9882
|
const GRID_ROW_ID_PREFIX = "gridRow_";
|
|
9767
9883
|
const GRID_CELL_ID_PREFIX = "gridCell_";
|
|
9768
9884
|
|
|
9769
|
-
/**
|
|
9770
|
-
* Зона захвата = зазор плюс небольшой заход на содержимое с обеих сторон.
|
|
9771
|
-
*
|
|
9772
|
-
* Смещение уводит дальний край ручки за зазор ровно на `HANDLE_BLEED_PX`, поэтому вместе с
|
|
9773
|
-
* шириной она оказывается симметрично «надета» на границу: от `-bleed` до `gap + bleed`.
|
|
9774
|
-
*/
|
|
9775
|
-
const handleSize = `calc(var(--grid-gap, 0px) + ${HANDLE_BLEED_PX * 2}px)`;
|
|
9776
|
-
const handleOffset = `calc(-1 * (var(--grid-gap, 0px) + ${HANDLE_BLEED_PX}px))`;
|
|
9777
|
-
/**
|
|
9778
|
-
* Ручка перетаскивания границы треков.
|
|
9779
|
-
*
|
|
9780
|
-
* Абсолютная и целиком лежит в зазоре, поэтому включение режима редактирования не сдвигает
|
|
9781
|
-
* раскладку ни на пиксель. Сам ползунок (`::after`) появляется только при наведении и во время
|
|
9782
|
-
* перетаскивания — в покое сетка выглядит как обычно.
|
|
9783
|
-
*/
|
|
9784
|
-
const ResizeHandle = styled.div.withConfig({ displayName: "ResizeHandle", componentId: "sc-1dtcak4" }) `
|
|
9785
|
-
position: absolute;
|
|
9786
|
-
z-index: 3;
|
|
9787
|
-
touch-action: none;
|
|
9788
|
-
|
|
9789
|
-
::after {
|
|
9790
|
-
content: "";
|
|
9791
|
-
position: absolute;
|
|
9792
|
-
border-radius: 0.0625rem;
|
|
9793
|
-
background: transparent;
|
|
9794
|
-
transition: background-color 120ms ease;
|
|
9795
|
-
}
|
|
9796
|
-
|
|
9797
|
-
:hover::after,
|
|
9798
|
-
&[data-dragging="true"]::after {
|
|
9799
|
-
background: ${({ theme: { palette } }) => palette.primary};
|
|
9800
|
-
}
|
|
9801
|
-
|
|
9802
|
-
${({ $axis }) => $axis === "column"
|
|
9803
|
-
? styled.css `
|
|
9804
|
-
top: 0;
|
|
9805
|
-
bottom: 0;
|
|
9806
|
-
right: ${handleOffset};
|
|
9807
|
-
width: ${handleSize};
|
|
9808
|
-
cursor: col-resize;
|
|
9809
|
-
|
|
9810
|
-
::after {
|
|
9811
|
-
top: 20%;
|
|
9812
|
-
bottom: 20%;
|
|
9813
|
-
left: 50%;
|
|
9814
|
-
width: 0.125rem;
|
|
9815
|
-
transform: translateX(-50%);
|
|
9816
|
-
}
|
|
9817
|
-
`
|
|
9818
|
-
: styled.css `
|
|
9819
|
-
left: 0;
|
|
9820
|
-
right: 0;
|
|
9821
|
-
bottom: ${handleOffset};
|
|
9822
|
-
height: ${handleSize};
|
|
9823
|
-
cursor: row-resize;
|
|
9824
|
-
|
|
9825
|
-
::after {
|
|
9826
|
-
left: 20%;
|
|
9827
|
-
right: 20%;
|
|
9828
|
-
top: 50%;
|
|
9829
|
-
height: 0.125rem;
|
|
9830
|
-
transform: translateY(-50%);
|
|
9831
|
-
}
|
|
9832
|
-
`};
|
|
9833
|
-
`;
|
|
9834
|
-
|
|
9835
9885
|
const getTemplateProperty = (axis) => axis === "row" ? "gridTemplateRows" : "gridTemplateColumns";
|
|
9836
9886
|
/**
|
|
9837
9887
|
* Пиксельные размеры треков отрисованной сетки.
|
|
@@ -9919,29 +9969,24 @@ const toShares = (pixels, total) => {
|
|
|
9919
9969
|
* снова раздувалась содержимым.
|
|
9920
9970
|
*/
|
|
9921
9971
|
const useGridHeightResize = ({ sizes, autoHeight, getGrid, onCommit, }) => {
|
|
9922
|
-
const [handle, setHandle] = React.useState(null);
|
|
9923
|
-
const [dragging, setDragging] = React.useState(false);
|
|
9924
|
-
const dragRef = React.useRef(null);
|
|
9925
9972
|
const sizesRef = React.useRef(sizes);
|
|
9926
9973
|
sizesRef.current = sizes;
|
|
9927
9974
|
/** Свойство корня, которым жест двигает нижнюю границу: жёсткая высота либо её минимум. */
|
|
9928
9975
|
const heightProperty = autoHeight ? "minHeight" : "height";
|
|
9929
|
-
const
|
|
9976
|
+
const onStart = React.useCallback((event) => {
|
|
9930
9977
|
const grid = getGrid();
|
|
9931
9978
|
const root = grid?.parentElement;
|
|
9932
9979
|
if (!grid || !root)
|
|
9933
|
-
return;
|
|
9980
|
+
return null;
|
|
9934
9981
|
const pixels = readTrackPixels(grid, "row");
|
|
9935
9982
|
if (!pixels?.length || pixels.length !== sizesRef.current.length)
|
|
9936
|
-
return;
|
|
9937
|
-
|
|
9938
|
-
dragRef.current = {
|
|
9983
|
+
return null;
|
|
9984
|
+
return {
|
|
9939
9985
|
start: event.clientY,
|
|
9940
9986
|
pixels,
|
|
9941
9987
|
root,
|
|
9942
9988
|
rootHeight: root.getBoundingClientRect().height,
|
|
9943
9989
|
};
|
|
9944
|
-
setDragging(true);
|
|
9945
9990
|
}, [getGrid]);
|
|
9946
9991
|
/** Пиксели треков после сдвига: меняется только последний, минимум — текущий, если он уже мал. */
|
|
9947
9992
|
const getNextPixels = React.useCallback((state, event) => {
|
|
@@ -9949,22 +9994,16 @@ const useGridHeightResize = ({ sizes, autoHeight, getGrid, onCommit, }) => {
|
|
|
9949
9994
|
const min = Math.min(MIN_TRACK_PX, last);
|
|
9950
9995
|
return [...state.pixels.slice(0, -1), Math.max(last + event.clientY - state.start, min)];
|
|
9951
9996
|
}, []);
|
|
9952
|
-
const
|
|
9953
|
-
const state = dragRef.current;
|
|
9997
|
+
const onMove = React.useCallback((state, event) => {
|
|
9954
9998
|
const grid = getGrid();
|
|
9955
|
-
if (!
|
|
9999
|
+
if (!grid)
|
|
9956
10000
|
return;
|
|
9957
10001
|
const pixels = getNextPixels(state, event);
|
|
9958
10002
|
const delta = pixels[pixels.length - 1] - state.pixels[state.pixels.length - 1];
|
|
9959
10003
|
state.root.style[heightProperty] = `${state.rootHeight + delta}px`;
|
|
9960
10004
|
grid.style[getTemplateProperty("row")] = buildTrackTemplate(pixels.map(size => `${size}px`), autoHeight);
|
|
9961
10005
|
}, [autoHeight, getGrid, getNextPixels, heightProperty]);
|
|
9962
|
-
const
|
|
9963
|
-
const state = dragRef.current;
|
|
9964
|
-
if (!state)
|
|
9965
|
-
return;
|
|
9966
|
-
dragRef.current = null;
|
|
9967
|
-
setDragging(false);
|
|
10006
|
+
const onEnd = React.useCallback((state, event) => {
|
|
9968
10007
|
const grid = getGrid();
|
|
9969
10008
|
if (grid)
|
|
9970
10009
|
grid.style[getTemplateProperty("row")] = "";
|
|
@@ -9976,8 +10015,7 @@ const useGridHeightResize = ({ sizes, autoHeight, getGrid, onCommit, }) => {
|
|
|
9976
10015
|
const total = sizesRef.current.reduce((value, size) => value + size, 0);
|
|
9977
10016
|
onCommit(`${Math.round(state.rootHeight + delta)}px`, toShares(pixels, total));
|
|
9978
10017
|
}, [getGrid, getNextPixels, heightProperty, onCommit]);
|
|
9979
|
-
|
|
9980
|
-
return { setHandle, dragging };
|
|
10018
|
+
return useResizeDrag({ onStart, onMove, onEnd });
|
|
9981
10019
|
};
|
|
9982
10020
|
|
|
9983
10021
|
/**
|
|
@@ -9990,7 +10028,7 @@ const GridHeightResizer = React.memo(({ sizes, autoHeight, getGrid, onCommit })
|
|
|
9990
10028
|
// Ручка лежит внутри трека: без остановки всплытия перетаскивание границы заканчивалось бы
|
|
9991
10029
|
// кликом по ячейке, то есть меняло бы выделение.
|
|
9992
10030
|
const stopPropagation = React.useCallback((event) => event.stopPropagation(), []);
|
|
9993
|
-
return (jsxRuntime.jsx(ResizeHandle, {
|
|
10031
|
+
return (jsxRuntime.jsx(ResizeHandle, { ref: setHandle, "$axis": "row", "data-dragging": dragging, onClick: stopPropagation }));
|
|
9994
10032
|
});
|
|
9995
10033
|
|
|
9996
10034
|
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
@@ -10011,31 +10049,26 @@ const buildPreviewTemplate = (pixels, index, first, second) => buildTrackTemplat
|
|
|
10011
10049
|
/**
|
|
10012
10050
|
* Перетаскивание границы между двумя соседними треками.
|
|
10013
10051
|
*
|
|
10014
|
-
*
|
|
10015
|
-
*
|
|
10016
|
-
* на отпускание мыши.
|
|
10052
|
+
* Сам жест — общий {@link useResizeDrag}: он же двигает и границы колонок таблицы, поэтому
|
|
10053
|
+
* ощущаются они одинаково. Здесь остаётся только грид-специфичное: во время жеста раскладка
|
|
10054
|
+
* меняется ИНЛАЙН-стилем на самом гриде, а в конфиг доли уходят один раз, на отпускание мыши.
|
|
10017
10055
|
*
|
|
10018
|
-
* Пиксельный снимок треков берётся однократно на
|
|
10056
|
+
* Пиксельный снимок треков берётся однократно на нажатии — если пересчитывать его на каждом
|
|
10019
10057
|
* шаге уже после применения новых долей, округление `fr → px → fr` даёт дрейф границы.
|
|
10020
10058
|
*/
|
|
10021
10059
|
const useGridResize = ({ axis, index, sizes, getGrid, onCommit }) => {
|
|
10022
|
-
const [handle, setHandle] = React.useState(null);
|
|
10023
|
-
const [dragging, setDragging] = React.useState(false);
|
|
10024
|
-
const dragRef = React.useRef(null);
|
|
10025
10060
|
const sizesRef = React.useRef(sizes);
|
|
10026
10061
|
sizesRef.current = sizes;
|
|
10027
10062
|
const isRow = axis === "row";
|
|
10028
10063
|
const getPosition = React.useCallback((event) => (isRow ? event.clientY : event.clientX), [isRow]);
|
|
10029
|
-
const
|
|
10064
|
+
const onStart = React.useCallback((event) => {
|
|
10030
10065
|
const grid = getGrid();
|
|
10031
10066
|
if (!grid)
|
|
10032
|
-
return;
|
|
10067
|
+
return null;
|
|
10033
10068
|
const pixels = readTrackPixels(grid, axis);
|
|
10034
10069
|
if (!pixels || index + 1 >= pixels.length)
|
|
10035
|
-
return;
|
|
10036
|
-
event
|
|
10037
|
-
dragRef.current = { start: getPosition(event), pixels };
|
|
10038
|
-
setDragging(true);
|
|
10070
|
+
return null;
|
|
10071
|
+
return { start: getPosition(event), pixels };
|
|
10039
10072
|
}, [axis, getGrid, getPosition, index]);
|
|
10040
10073
|
/** Новый пиксельный размер первого трека пары с учётом минимального размера обоих. */
|
|
10041
10074
|
const getNextFirstSize = React.useCallback((state, event) => {
|
|
@@ -10046,10 +10079,9 @@ const useGridResize = ({ axis, index, sizes, getGrid, onCommit }) => {
|
|
|
10046
10079
|
const min = getMinTrackSize(total);
|
|
10047
10080
|
return clamp(first + getPosition(event) - state.start, min, total - min);
|
|
10048
10081
|
}, [getPosition, index]);
|
|
10049
|
-
const
|
|
10050
|
-
const state = dragRef.current;
|
|
10082
|
+
const onMove = React.useCallback((state, event) => {
|
|
10051
10083
|
const grid = getGrid();
|
|
10052
|
-
if (!
|
|
10084
|
+
if (!grid)
|
|
10053
10085
|
return;
|
|
10054
10086
|
const first = getNextFirstSize(state, event);
|
|
10055
10087
|
if (first === null)
|
|
@@ -10057,12 +10089,7 @@ const useGridResize = ({ axis, index, sizes, getGrid, onCommit }) => {
|
|
|
10057
10089
|
const total = state.pixels[index] + state.pixels[index + 1];
|
|
10058
10090
|
grid.style[getTemplateProperty(axis)] = buildPreviewTemplate(state.pixels, index, first, total - first);
|
|
10059
10091
|
}, [axis, getGrid, getNextFirstSize, index]);
|
|
10060
|
-
const
|
|
10061
|
-
const state = dragRef.current;
|
|
10062
|
-
if (!state)
|
|
10063
|
-
return;
|
|
10064
|
-
dragRef.current = null;
|
|
10065
|
-
setDragging(false);
|
|
10092
|
+
const onEnd = React.useCallback((state, event) => {
|
|
10066
10093
|
const grid = getGrid();
|
|
10067
10094
|
if (grid)
|
|
10068
10095
|
grid.style[getTemplateProperty(axis)] = "";
|
|
@@ -10077,8 +10104,7 @@ const useGridResize = ({ axis, index, sizes, getGrid, onCommit }) => {
|
|
|
10077
10104
|
return;
|
|
10078
10105
|
onCommit(index, [nextFirst, roundFr(share - nextFirst)]);
|
|
10079
10106
|
}, [axis, getGrid, getNextFirstSize, index, onCommit]);
|
|
10080
|
-
|
|
10081
|
-
return { setHandle, dragging };
|
|
10107
|
+
return useResizeDrag({ onStart, onMove, onEnd });
|
|
10082
10108
|
};
|
|
10083
10109
|
|
|
10084
10110
|
const GridResizer = React.memo(({ axis, index, sizes, getGrid, onCommit }) => {
|
|
@@ -10086,7 +10112,7 @@ const GridResizer = React.memo(({ axis, index, sizes, getGrid, onCommit }) => {
|
|
|
10086
10112
|
// Ручка лежит внутри трека, и без этого перетаскивание границы заканчивалось бы кликом
|
|
10087
10113
|
// по ячейке — то есть меняло бы выделение.
|
|
10088
10114
|
const stopPropagation = React.useCallback((event) => event.stopPropagation(), []);
|
|
10089
|
-
return (jsxRuntime.jsx(ResizeHandle, {
|
|
10115
|
+
return (jsxRuntime.jsx(ResizeHandle, { ref: setHandle, "$axis": axis, "data-dragging": dragging, onClick: stopPropagation }));
|
|
10090
10116
|
});
|
|
10091
10117
|
|
|
10092
10118
|
/**
|
|
@@ -11407,10 +11433,17 @@ const toSchemaAttribute = (params) => {
|
|
|
11407
11433
|
attributeName,
|
|
11408
11434
|
// Тип принадлежит источнику: конфиг задаёт его только там, где источника нет.
|
|
11409
11435
|
type: source?.type ?? description?.type ?? DEFAULT_ATTRIBUTE_TYPE,
|
|
11436
|
+
// Уточнение типа — целиком за конфигом: у атрибута источника поля `subType` нет.
|
|
11437
|
+
subType: description?.subType,
|
|
11410
11438
|
alias: description?.alias ?? source?.alias ?? attributeName,
|
|
11411
11439
|
description: description?.description ?? source?.description,
|
|
11412
11440
|
isEditable: resolveIsEditable(params),
|
|
11413
11441
|
stringFormat: resolveStringFormat(description, source),
|
|
11442
|
+
// Раскладка колонки живёт только в конфиге контейнера: источник о ширине таблицы не знает.
|
|
11443
|
+
width: description?.width,
|
|
11444
|
+
resizable: description?.resizable,
|
|
11445
|
+
multiline: description?.multiline,
|
|
11446
|
+
style: description?.style,
|
|
11414
11447
|
};
|
|
11415
11448
|
};
|
|
11416
11449
|
/**
|
|
@@ -15441,6 +15474,8 @@ const TIME_FORMAT = /hh:mm/;
|
|
|
15441
15474
|
* закрытие редактора по клику снаружи обязано этот контейнер пропускать.
|
|
15442
15475
|
*/
|
|
15443
15476
|
const PORTAL_ROOT_SELECTOR = "#portal-root";
|
|
15477
|
+
/** Ниже этой ширины колонку не ужать мышью: уже неё в ячейке не остаётся места под значение. */
|
|
15478
|
+
const MIN_COLUMN_WIDTH = 48;
|
|
15444
15479
|
|
|
15445
15480
|
/**
|
|
15446
15481
|
* Собственный бокс таблицы. Без размеров в `options` представления узел ничего не меняет:
|
|
@@ -15497,10 +15532,13 @@ const TableHead = styled.thead.withConfig({ displayName: "TableHead", componentI
|
|
|
15497
15532
|
z-index: 1;
|
|
15498
15533
|
`;
|
|
15499
15534
|
const TableHeadCell = styled.th.withConfig({ displayName: "TableHeadCell", componentId: "sc-1m6xil2" }) `
|
|
15535
|
+
/* Относительное позиционирование держит захват границы колонки. */
|
|
15536
|
+
position: relative;
|
|
15500
15537
|
padding: 0.375rem 0.5rem;
|
|
15501
15538
|
text-align: left;
|
|
15502
15539
|
font-weight: 600;
|
|
15503
15540
|
white-space: nowrap;
|
|
15541
|
+
overflow: hidden;
|
|
15504
15542
|
color: ${({ theme }) => theme.palette.textSecondary};
|
|
15505
15543
|
/* Рамка принадлежит ячейке (рамки раздельные), поэтому едет вместе с липкой шапкой. */
|
|
15506
15544
|
border-bottom: 1px solid ${({ theme }) => theme.palette.elementDeep};
|
|
@@ -15511,10 +15549,17 @@ const TableHeadCell = styled.th.withConfig({ displayName: "TableHeadCell", compo
|
|
|
15511
15549
|
user-select: none;
|
|
15512
15550
|
`}
|
|
15513
15551
|
`;
|
|
15552
|
+
/**
|
|
15553
|
+
* Содержимое заголовка. Псевдоним обрезается многоточием, значок сортировки не сжимается:
|
|
15554
|
+
* у колонки с заданной шириной длинный псевдоним иначе выдавил бы значок за край ячейки.
|
|
15555
|
+
*/
|
|
15514
15556
|
const TableHeadContent = styled.span.withConfig({ displayName: "TableHeadContent", componentId: "sc-8hzcop" }) `
|
|
15515
|
-
display:
|
|
15557
|
+
display: flex;
|
|
15516
15558
|
align-items: center;
|
|
15517
15559
|
gap: 0.25rem;
|
|
15560
|
+
min-width: 0;
|
|
15561
|
+
overflow: hidden;
|
|
15562
|
+
text-overflow: ellipsis;
|
|
15518
15563
|
`;
|
|
15519
15564
|
/**
|
|
15520
15565
|
* Место под значок сортировки. Занято всегда, пока сортировка включена, а сам значок только
|
|
@@ -15554,13 +15599,31 @@ const TableActionsCell = styled(TableCellWrapper).withConfig({ displayName: "Tab
|
|
|
15554
15599
|
width: 2rem;
|
|
15555
15600
|
text-align: right;
|
|
15556
15601
|
`;
|
|
15602
|
+
/**
|
|
15603
|
+
* Как значение ведёт себя, когда не помещается в колонку: режется многоточием в одну строку
|
|
15604
|
+
* или переносится по словам, растягивая строку таблицы вниз (`multiline` атрибута).
|
|
15605
|
+
*
|
|
15606
|
+
* Перенос разрешаем и посреди слова: колонка бывает уже одного длинного слова (ссылка, артикул),
|
|
15607
|
+
* и без этого оно вылезло бы за заданную ширину, сделав её бессмысленной.
|
|
15608
|
+
*/
|
|
15609
|
+
const cellWrapMixin = styled.css `
|
|
15610
|
+
${({ $multiline }) => $multiline
|
|
15611
|
+
? styled.css `
|
|
15612
|
+
white-space: normal;
|
|
15613
|
+
overflow-wrap: anywhere;
|
|
15614
|
+
`
|
|
15615
|
+
: styled.css `
|
|
15616
|
+
overflow: hidden;
|
|
15617
|
+
text-overflow: ellipsis;
|
|
15618
|
+
white-space: nowrap;
|
|
15619
|
+
`}
|
|
15620
|
+
`;
|
|
15557
15621
|
const CellText = styled.div.withConfig({ displayName: "CellText", componentId: "sc-ok630s" }) `
|
|
15558
15622
|
padding: 0.375rem 0.25rem;
|
|
15559
15623
|
/* Та же высота строки, что у редактора — иначе строка подпрыгивала бы по вертикали. */
|
|
15560
15624
|
line-height: 1.25rem;
|
|
15561
|
-
|
|
15562
|
-
|
|
15563
|
-
white-space: nowrap;
|
|
15625
|
+
|
|
15626
|
+
${cellWrapMixin};
|
|
15564
15627
|
`;
|
|
15565
15628
|
/**
|
|
15566
15629
|
* Редактируемая ячейка вне фокуса. Показывает значение по `stringFormat`, а редактор
|
|
@@ -15580,9 +15643,8 @@ const CellButton = styled.button.withConfig({ displayName: "CellButton", compone
|
|
|
15580
15643
|
color: inherit;
|
|
15581
15644
|
text-align: left;
|
|
15582
15645
|
cursor: text;
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
white-space: nowrap;
|
|
15646
|
+
|
|
15647
|
+
${cellWrapMixin};
|
|
15586
15648
|
|
|
15587
15649
|
:hover {
|
|
15588
15650
|
border-color: ${({ theme }) => theme.palette.elementDeep};
|
|
@@ -15607,8 +15669,9 @@ const CellGhost = styled.span.withConfig({ displayName: "CellGhost", componentId
|
|
|
15607
15669
|
/* Рамка повторяет CellButton — с ней высота ячейки в правке совпадает с высотой на чтении. */
|
|
15608
15670
|
border: 1px solid transparent;
|
|
15609
15671
|
line-height: 1.25rem;
|
|
15610
|
-
white-space: nowrap;
|
|
15611
15672
|
visibility: hidden;
|
|
15673
|
+
|
|
15674
|
+
${cellWrapMixin};
|
|
15612
15675
|
`;
|
|
15613
15676
|
const CellField = styled.div.withConfig({ displayName: "CellField", componentId: "sc-1ww3875" }) `
|
|
15614
15677
|
position: absolute;
|
|
@@ -15625,11 +15688,97 @@ const CellField = styled.div.withConfig({ displayName: "CellField", componentId:
|
|
|
15625
15688
|
min-width: 0;
|
|
15626
15689
|
}
|
|
15627
15690
|
`;
|
|
15628
|
-
|
|
15691
|
+
/**
|
|
15692
|
+
* Ячейка со вложениями. Список идёт колонкой, кнопка добавления — под ним, поэтому строка растёт
|
|
15693
|
+
* ровно на столько, сколько файлов в ней лежит.
|
|
15694
|
+
*
|
|
15695
|
+
* Отступы те же, что у текстовой ячейки (`CellText`): в одном ряду с обычными колонками значения
|
|
15696
|
+
* обязаны стоять на одной линии.
|
|
15697
|
+
*/
|
|
15698
|
+
const AttachmentsCellBox = styled.div.withConfig({ displayName: "AttachmentsCellBox", componentId: "sc-1ayq7sq" }) `
|
|
15699
|
+
display: flex;
|
|
15700
|
+
flex-direction: column;
|
|
15701
|
+
align-items: flex-start;
|
|
15702
|
+
gap: 0.25rem;
|
|
15703
|
+
padding: 0.375rem 0.25rem;
|
|
15704
|
+
`;
|
|
15705
|
+
const TableEmpty = styled.div.withConfig({ displayName: "TableEmpty", componentId: "sc-dmdhrv" }) `
|
|
15629
15706
|
padding: 0.75rem 0.25rem;
|
|
15630
15707
|
color: ${({ theme }) => theme.palette.textSecondary};
|
|
15631
15708
|
`;
|
|
15632
15709
|
|
|
15710
|
+
/**
|
|
15711
|
+
* Ячейка с вложениями: разбор значения, просмотр, скачивание и правка списка.
|
|
15712
|
+
*
|
|
15713
|
+
* Значение атрибута — строка: файлы приезжают в ней списком JSON, поэтому и обратно в черновик
|
|
15714
|
+
* кладём строку, а не массив — иначе тип ячейки менялся бы от самой правки.
|
|
15715
|
+
*
|
|
15716
|
+
* Добавление здесь только по ссылке. Загрузка с диска требует ресурса-родителя и файлового
|
|
15717
|
+
* api контейнера вложений, а у колонки таблицы ни того, ни другого нет.
|
|
15718
|
+
*/
|
|
15719
|
+
const useAttachmentsCell = ({ attribute, row, canEdit, onChange }) => {
|
|
15720
|
+
const [previewIndex, setPreviewIndex] = React.useState(null);
|
|
15721
|
+
const [isLinkDialogOpen, setLinkDialogOpen] = React.useState(false);
|
|
15722
|
+
const { attributeName, isEditable } = attribute;
|
|
15723
|
+
const value = row.properties[attributeName];
|
|
15724
|
+
const items = React.useMemo(() => parseAttachments(value), [value]);
|
|
15725
|
+
const previewImages = useAttachmentPreviewImages({ items, active: previewIndex !== null });
|
|
15726
|
+
const downloadByIndex = useAttachmentDownload(items);
|
|
15727
|
+
// Те же два условия, что и у обычной ячейки: режим таблицы и разрешение самого атрибута.
|
|
15728
|
+
const editable = canEdit && isEditable;
|
|
15729
|
+
const persist = React.useCallback((next) => onChange(row.key, attributeName, JSON.stringify(next)), [attributeName, onChange, row.key]);
|
|
15730
|
+
const onPreview = React.useCallback((link) => {
|
|
15731
|
+
const index = items.findIndex(item => item.link === link);
|
|
15732
|
+
if (index >= 0) {
|
|
15733
|
+
setPreviewIndex(index);
|
|
15734
|
+
}
|
|
15735
|
+
}, [items]);
|
|
15736
|
+
const onClosePreview = React.useCallback(() => setPreviewIndex(null), []);
|
|
15737
|
+
const onDownload = React.useCallback((_image, index) => downloadByIndex(index), [downloadByIndex]);
|
|
15738
|
+
const onDelete = React.useCallback((link) => persist(items.filter(item => item.link !== link)), [items, persist]);
|
|
15739
|
+
const onOpenLinkDialog = React.useCallback(() => setLinkDialogOpen(true), []);
|
|
15740
|
+
const onCloseLinkDialog = React.useCallback(() => setLinkDialogOpen(false), []);
|
|
15741
|
+
const onAddByLink = React.useCallback((url) => persist([
|
|
15742
|
+
...items,
|
|
15743
|
+
{
|
|
15744
|
+
link: url,
|
|
15745
|
+
name: getFileNameFromUrl(url),
|
|
15746
|
+
mimeType: getMimeTypeFromUrl(url),
|
|
15747
|
+
date: new Date().toISOString(),
|
|
15748
|
+
isExternal: true,
|
|
15749
|
+
},
|
|
15750
|
+
]), [items, persist]);
|
|
15751
|
+
return {
|
|
15752
|
+
items,
|
|
15753
|
+
editable,
|
|
15754
|
+
previewIndex,
|
|
15755
|
+
previewImages,
|
|
15756
|
+
isLinkDialogOpen,
|
|
15757
|
+
onPreview,
|
|
15758
|
+
onClosePreview,
|
|
15759
|
+
onDownload,
|
|
15760
|
+
onDelete,
|
|
15761
|
+
onOpenLinkDialog,
|
|
15762
|
+
onCloseLinkDialog,
|
|
15763
|
+
onAddByLink,
|
|
15764
|
+
};
|
|
15765
|
+
};
|
|
15766
|
+
|
|
15767
|
+
/**
|
|
15768
|
+
* Колонка со вложениями: список файлов прямо в ячейке.
|
|
15769
|
+
*
|
|
15770
|
+
* Тип у атрибута строковый, вложениями его делает `subType: "Attachments"` в схеме контейнера —
|
|
15771
|
+
* поэтому ветка стоит раньше разбора типа и обычных редакторов ячейки.
|
|
15772
|
+
*/
|
|
15773
|
+
const AttachmentsCell = React.memo(({ attribute, row, canEdit, onChange }) => {
|
|
15774
|
+
const { t } = useGlobalContext();
|
|
15775
|
+
const { items, editable, previewIndex, previewImages, isLinkDialogOpen, onPreview, onClosePreview, onDownload, onDelete, onOpenLinkDialog, onCloseLinkDialog, onAddByLink, } = useAttachmentsCell({ attribute, row, canEdit, onChange });
|
|
15776
|
+
return (jsxRuntime.jsxs(AttachmentsCellBox, { children: [!items.length && !editable && jsxRuntime.jsx(CellPlaceholder, { children: "\u2014" }), jsxRuntime.jsx(AttachmentsList, { items: items, isEdit: editable, onPreview: onPreview, onDelete: onDelete }), editable && (jsxRuntime.jsx(uilibGl.IconButton, { kind: "link", tabIndex: 0, title: t("attachments.fromLink", { ns: "common", defaultValue: "По ссылке" }), onClick: onOpenLinkDialog })), jsxRuntime.jsx(AttachmentLinkDialog, { isOpen: isLinkDialogOpen, onClose: onCloseLinkDialog, onSubmit: onAddByLink }), previewIndex !== null && (jsxRuntime.jsx(uilibGl.Preview, { images: previewImages, initialIndex: previewIndex, isOpen: true, onClose: onClosePreview, onDownload: onDownload, errorTitleText: t("attachments.resourceUnavailable", {
|
|
15777
|
+
ns: "common",
|
|
15778
|
+
defaultValue: "Ресурс недоступен",
|
|
15779
|
+
}) }, previewIndex))] }));
|
|
15780
|
+
});
|
|
15781
|
+
|
|
15633
15782
|
/**
|
|
15634
15783
|
* Значение ячейки не изменилось.
|
|
15635
15784
|
*
|
|
@@ -15728,7 +15877,7 @@ const useCellEditing = () => {
|
|
|
15728
15877
|
const TableCell = React.memo(({ attribute, row, canEdit, onChange }) => {
|
|
15729
15878
|
const { t, language } = useGlobalContext();
|
|
15730
15879
|
const { editing, buttonProps, editorProps } = useCellEditing();
|
|
15731
|
-
const { attributeName, type, isEditable, stringFormat } = attribute;
|
|
15880
|
+
const { attributeName, type, subType, isEditable, stringFormat, multiline } = attribute;
|
|
15732
15881
|
const value = row.properties[attributeName];
|
|
15733
15882
|
const handleChange = React.useCallback((next) => {
|
|
15734
15883
|
// Пустые правки отбрасываем: иначе строка становилась бы «изменённой» от простого
|
|
@@ -15747,16 +15896,21 @@ const TableCell = React.memo(({ attribute, row, canEdit, onChange }) => {
|
|
|
15747
15896
|
const withTime = React.useMemo(() => TIME_FORMAT.test(stringFormat?.format ?? ""), [stringFormat?.format]);
|
|
15748
15897
|
// `canEdit` — режим таблицы, `isEditable` — разрешение конкретного атрибута: правка требует обоих.
|
|
15749
15898
|
const editable = canEdit && isEditable && EDITABLE_ATTRIBUTE_TYPES.includes(type);
|
|
15899
|
+
// Вложения — это строка с JSON, а не текст: разбирать и рисовать её нужно раньше любых
|
|
15900
|
+
// редакторов по типу, иначе в ячейке оказался бы сырой список файлов.
|
|
15901
|
+
if (subType === api.StringSubType.Attachments) {
|
|
15902
|
+
return jsxRuntime.jsx(AttachmentsCell, { attribute: attribute, row: row, canEdit: canEdit, onChange: onChange });
|
|
15903
|
+
}
|
|
15750
15904
|
// Логическое значение и на чтение показываем галкой, а не словами «true»/«false» —
|
|
15751
15905
|
// так же, как остальной дашборд рисует булевы атрибуты.
|
|
15752
15906
|
if (type === api.AttributeType.Boolean) {
|
|
15753
15907
|
return jsxRuntime.jsx(uilibGl.Checkbox, { checked: !!value, disabled: !editable, onChange: () => handleChange(!value) });
|
|
15754
15908
|
}
|
|
15755
15909
|
if (!editable) {
|
|
15756
|
-
return jsxRuntime.jsx(CellText, { title: formatted, children: formatted || jsxRuntime.jsx(CellPlaceholder, { children: "\u2014" }) });
|
|
15910
|
+
return (jsxRuntime.jsx(CellText, { "$multiline": multiline, title: formatted, children: formatted || jsxRuntime.jsx(CellPlaceholder, { children: "\u2014" }) }));
|
|
15757
15911
|
}
|
|
15758
15912
|
if (!editing) {
|
|
15759
|
-
return (jsxRuntime.jsx(CellButton, { type: "button", ...buttonProps, children: formatted || jsxRuntime.jsx(CellPlaceholder, { children: "\u2014" }) }));
|
|
15913
|
+
return (jsxRuntime.jsx(CellButton, { type: "button", "$multiline": multiline, ...buttonProps, children: formatted || jsxRuntime.jsx(CellPlaceholder, { children: "\u2014" }) }));
|
|
15760
15914
|
}
|
|
15761
15915
|
const renderEditor = () => {
|
|
15762
15916
|
if (type === api.AttributeType.DateTime) {
|
|
@@ -15767,9 +15921,105 @@ const TableCell = React.memo(({ attribute, row, canEdit, onChange }) => {
|
|
|
15767
15921
|
}
|
|
15768
15922
|
return (jsxRuntime.jsx(uilibGl.Input, { autoFocus: true, value: value === null || value === undefined ? "" : String(value), width: "100%", onChange: ({ target }) => handleChange(target.value) }));
|
|
15769
15923
|
};
|
|
15770
|
-
return (jsxRuntime.jsxs(CellEditor, { ...editorProps, children: [jsxRuntime.jsx(CellGhost, { children: formatted || "—" }), jsxRuntime.jsx(CellField, { children: renderEditor() })] }));
|
|
15924
|
+
return (jsxRuntime.jsxs(CellEditor, { ...editorProps, children: [jsxRuntime.jsx(CellGhost, { "$multiline": multiline, children: formatted || "—" }), jsxRuntime.jsx(CellField, { children: renderEditor() })] }));
|
|
15925
|
+
});
|
|
15926
|
+
|
|
15927
|
+
/**
|
|
15928
|
+
* Имя переменной ширины колонки.
|
|
15929
|
+
*
|
|
15930
|
+
* Через неё идёт и предпросмотр перетаскивания, и итоговая ширина: пока переменная не задана,
|
|
15931
|
+
* работает запасное значение из стиля ячейки, а жест на время перетаскивания объявляет её на
|
|
15932
|
+
* самой таблице — одним свойством на один узел, как сетка меняет свой шаблон треков.
|
|
15933
|
+
*/
|
|
15934
|
+
const getColumnWidthVariable = (index) => `--table-col-${index}`;
|
|
15935
|
+
/**
|
|
15936
|
+
* Инлайн-размер ячейки колонки с заданной или изменяемой шириной.
|
|
15937
|
+
*
|
|
15938
|
+
* Раскладка таблицы автоматическая (ширину колонок выбирает содержимое), и одной `width` ей мало —
|
|
15939
|
+
* она для неё лишь пожелание. Жёстко колонку держат все три предела сразу, поэтому и ставим их
|
|
15940
|
+
* втроём, на каждую ячейку колонки.
|
|
15941
|
+
*
|
|
15942
|
+
* Ни ширины, ни разрешения тянуть — стиля нет: колонка остаётся на попечении раскладки, как и была.
|
|
15943
|
+
*/
|
|
15944
|
+
const getColumnStyle = (index, width, resizable) => {
|
|
15945
|
+
if (width == null && !resizable) {
|
|
15946
|
+
return undefined;
|
|
15947
|
+
}
|
|
15948
|
+
const variable = getColumnWidthVariable(index);
|
|
15949
|
+
const fixed = width == null ? null : `${width}px`;
|
|
15950
|
+
return {
|
|
15951
|
+
width: `var(${variable}, ${fixed ?? "auto"})`,
|
|
15952
|
+
minWidth: `var(${variable}, ${fixed ?? "0"})`,
|
|
15953
|
+
// Без заданной ширины предел остаётся общим для всех ячеек — тем, что задан стилем таблицы.
|
|
15954
|
+
maxWidth: `var(${variable}, ${fixed ?? "var(--table-cell-max-width, 20rem)"})`,
|
|
15955
|
+
};
|
|
15956
|
+
};
|
|
15957
|
+
|
|
15958
|
+
/**
|
|
15959
|
+
* Перетаскивание правой границы колонки.
|
|
15960
|
+
*
|
|
15961
|
+
* Жест — общий {@link useResizeDrag}, тот же, что двигает границы ячеек сетки дашборда. Здесь
|
|
15962
|
+
* остаётся только табличное: во время жеста ширина рисуется переменной на самой таблице, а в
|
|
15963
|
+
* состояние уходит один раз, на отпускание мыши. Так шаг жеста меняет одно свойство одного узла,
|
|
15964
|
+
* а не перерисовывает таблицу целиком.
|
|
15965
|
+
*
|
|
15966
|
+
* Стартовая ширина берётся с самой ячейки заголовка: у колонки без `width` её выбрала раскладка
|
|
15967
|
+
* таблицы, и тянуть надо от того, что видно.
|
|
15968
|
+
*/
|
|
15969
|
+
const useColumnResize = ({ index, onCommit }) => {
|
|
15970
|
+
const variable = getColumnWidthVariable(index);
|
|
15971
|
+
const onStart = React.useCallback((event) => {
|
|
15972
|
+
// Обработчик нажатия висит на самой ручке, поэтому её ячейка и таблица достаются отсюда —
|
|
15973
|
+
// отдельный ref на них не нужен.
|
|
15974
|
+
const cell = event.currentTarget?.closest("th");
|
|
15975
|
+
if (!cell) {
|
|
15976
|
+
return null;
|
|
15977
|
+
}
|
|
15978
|
+
return {
|
|
15979
|
+
start: event.clientX,
|
|
15980
|
+
width: cell.getBoundingClientRect().width,
|
|
15981
|
+
table: cell.closest("table"),
|
|
15982
|
+
};
|
|
15983
|
+
}, []);
|
|
15984
|
+
const getNextWidth = React.useCallback((state, event) => Math.max(MIN_COLUMN_WIDTH, Math.round(state.width + event.clientX - state.start)), []);
|
|
15985
|
+
const onMove = React.useCallback((state, event) => state.table?.style.setProperty(variable, `${getNextWidth(state, event)}px`), [getNextWidth, variable]);
|
|
15986
|
+
const onEnd = React.useCallback((state, event) => {
|
|
15987
|
+
state.table?.style.removeProperty(variable);
|
|
15988
|
+
const next = getNextWidth(state, event);
|
|
15989
|
+
// Клик по границе без перетаскивания ширину менять не должен.
|
|
15990
|
+
if (next === Math.round(state.width)) {
|
|
15991
|
+
return;
|
|
15992
|
+
}
|
|
15993
|
+
onCommit(next);
|
|
15994
|
+
}, [getNextWidth, onCommit, variable]);
|
|
15995
|
+
return useResizeDrag({ onStart, onMove, onEnd });
|
|
15996
|
+
};
|
|
15997
|
+
|
|
15998
|
+
/**
|
|
15999
|
+
* Ручка ширины колонки на правой границе заголовка.
|
|
16000
|
+
*
|
|
16001
|
+
* Тот же компонент и тот же жест, что двигают границы ячеек сетки дашборда: пользователю это одно
|
|
16002
|
+
* и то же действие, и выглядеть оно обязано одинаково.
|
|
16003
|
+
*/
|
|
16004
|
+
const TableColumnResizer = React.memo(({ index, onCommit }) => {
|
|
16005
|
+
const { setHandle, dragging } = useColumnResize({ index, onCommit });
|
|
16006
|
+
// Ручка лежит внутри заголовка, и без остановки всплытия перетаскивание границы заканчивалось бы
|
|
16007
|
+
// кликом по нему — то есть переключало бы сортировку.
|
|
16008
|
+
const stopPropagation = React.useCallback((event) => event.stopPropagation(), []);
|
|
16009
|
+
return jsxRuntime.jsx(ResizeHandle, { ref: setHandle, "$axis": "column", "data-dragging": dragging, onClick: stopPropagation });
|
|
15771
16010
|
});
|
|
15772
16011
|
|
|
16012
|
+
/**
|
|
16013
|
+
* Шапка таблицы: заголовки колонок, сортировка кликом и захват границы для ручной ширины.
|
|
16014
|
+
*
|
|
16015
|
+
* Захват рисуется по разрешению атрибута (`resizable`) и не смотрит на режим правки контейнера:
|
|
16016
|
+
* ширина колонки — это вид, а не данные.
|
|
16017
|
+
*/
|
|
16018
|
+
const TableHeadRow = ({ columns, sort, sortEnabled, columnWidths, withActionsColumn, onSortToggle, onColumnResize, }) => (jsxRuntime.jsxs("tr", { children: [columns.map(({ attributeName, alias, description, resizable }, index) => {
|
|
16019
|
+
const sorted = sort?.attributeName === attributeName;
|
|
16020
|
+
return (jsxRuntime.jsxs(TableHeadCell, { title: description || alias, style: getColumnStyle(index, columnWidths[attributeName], resizable), "$sortable": sortEnabled, onClick: sortEnabled ? () => onSortToggle(attributeName) : undefined, children: [jsxRuntime.jsxs(TableHeadContent, { children: [alias, sortEnabled && (jsxRuntime.jsx(TableSortSlot, { "$active": sorted, children: jsxRuntime.jsx(uilibGl.Icon, { kind: sorted && sort?.direction === "desc" ? "sorting_des" : "sorting_asc" }) }))] }), resizable && (jsxRuntime.jsx(TableColumnResizer, { index: index, onCommit: nextWidth => onColumnResize(attributeName, nextWidth) }))] }, attributeName));
|
|
16021
|
+
}), withActionsColumn && jsxRuntime.jsx(TableHeadCell, {})] }));
|
|
16022
|
+
|
|
15773
16023
|
const ContainerLoading = () => (jsxRuntime.jsx(uilibGl.Flex, { alignContent: "center", justifyContent: "center", width: "100%", children: jsxRuntime.jsx(uilibGl.CircularProgress, { diameter: 1.5, mono: true }) }));
|
|
15774
16024
|
|
|
15775
16025
|
/**
|
|
@@ -15885,16 +16135,24 @@ const getTableBoxStyle = (width, height) => {
|
|
|
15885
16135
|
*
|
|
15886
16136
|
* `width`/`height` — размер собственного бокса таблицы: он ограничивает её саму и прокручивает
|
|
15887
16137
|
* то, что не влезло. Место, отведённое контейнеру, задаётся его же опциями и живёт отдельно.
|
|
16138
|
+
*
|
|
16139
|
+
* Ширины колонок — тоже вид: конфиг задаёт стартовую, растянутая мышью живёт здесь и в конфиг
|
|
16140
|
+
* не возвращается.
|
|
15888
16141
|
*/
|
|
15889
16142
|
const useTableView = (elementConfig) => {
|
|
15890
16143
|
const context = React.useContext(StructuredDataContext);
|
|
15891
16144
|
const { sort: sortEnabled, width, height } = elementConfig?.options || {};
|
|
15892
16145
|
const [sort, setSort] = React.useState(null);
|
|
16146
|
+
const [widths, setWidths] = React.useState({});
|
|
15893
16147
|
const schema = context?.schema;
|
|
15894
16148
|
const rows = context?.rows;
|
|
15895
16149
|
const columns = React.useMemo(() => schema ?? [], [schema]);
|
|
15896
16150
|
const sortedRows = React.useMemo(() => sortRows(rows ?? [], sort, schema ?? []), [rows, schema, sort]);
|
|
15897
16151
|
const sizeCss = React.useMemo(() => getTableBoxStyle(width, height), [height, width]);
|
|
16152
|
+
// Растянутая мышью ширина живёт только в рантайме и перебивает конфигурационную: последнее
|
|
16153
|
+
// слово за тем, кто тянул. Обратно в конфиг она не пишется.
|
|
16154
|
+
const columnWidths = React.useMemo(() => columns.reduce((acc, attribute) => ({ ...acc, [attribute.attributeName]: widths[attribute.attributeName] ?? attribute.width }), {}), [columns, widths]);
|
|
16155
|
+
const onColumnResize = React.useCallback((attributeName, nextWidth) => setWidths(current => ({ ...current, [attributeName]: nextWidth })), []);
|
|
15898
16156
|
const onSortToggle = React.useCallback((attributeName) => setSort(current => getNextSort(current, attributeName)), []);
|
|
15899
16157
|
return {
|
|
15900
16158
|
context,
|
|
@@ -15905,7 +16163,9 @@ const useTableView = (elementConfig) => {
|
|
|
15905
16163
|
sizeCss,
|
|
15906
16164
|
// Ширина задана — колонки по содержимому: лишнее уходит в прокрутку бокса, а не в многоточие.
|
|
15907
16165
|
contentWidth: width != null,
|
|
16166
|
+
columnWidths,
|
|
15908
16167
|
onSortToggle,
|
|
16168
|
+
onColumnResize,
|
|
15909
16169
|
};
|
|
15910
16170
|
};
|
|
15911
16171
|
|
|
@@ -15917,7 +16177,7 @@ const useTableView = (elementConfig) => {
|
|
|
15917
16177
|
*/
|
|
15918
16178
|
const ElementTable = React.memo(({ elementConfig }) => {
|
|
15919
16179
|
const { t } = useGlobalContext();
|
|
15920
|
-
const { context, columns, rows, sort, sortEnabled, sizeCss, contentWidth, onSortToggle } = useTableView(elementConfig);
|
|
16180
|
+
const { context, columns, rows, sort, sortEnabled, sizeCss, contentWidth, columnWidths, onSortToggle, onColumnResize, } = useTableView(elementConfig);
|
|
15921
16181
|
// Узел таблицы держим состоянием, а не ref: отсечка должна встать сразу, как он появится.
|
|
15922
16182
|
const [table, setTable] = React.useState(null);
|
|
15923
16183
|
useHeadOverlap(table);
|
|
@@ -15933,10 +16193,13 @@ const ElementTable = React.memo(({ elementConfig }) => {
|
|
|
15933
16193
|
defaultValue: "Схема данных не задана",
|
|
15934
16194
|
}) }));
|
|
15935
16195
|
}
|
|
15936
|
-
return (jsxRuntime.jsx(TableBox, { "$sizeCss": sizeCss, children: jsxRuntime.jsxs(TableWrapper, { ref: setTable, "$contentWidth": contentWidth, children: [jsxRuntime.jsx(TableHead, { children: jsxRuntime.jsxs(
|
|
15937
|
-
|
|
15938
|
-
|
|
15939
|
-
|
|
16196
|
+
return (jsxRuntime.jsx(TableBox, { "$sizeCss": sizeCss, children: jsxRuntime.jsxs(TableWrapper, { ref: setTable, "$contentWidth": contentWidth, children: [jsxRuntime.jsx(TableHead, { children: jsxRuntime.jsx(TableHeadRow, { columns: columns, sort: sort, sortEnabled: sortEnabled, columnWidths: columnWidths, withActionsColumn: context.canDelete, onSortToggle: onSortToggle, onColumnResize: onColumnResize }) }), jsxRuntime.jsx(TableBody, { children: rows.map(row => (jsxRuntime.jsxs(TableRow, { children: [columns.map((attribute, columnIndex) => (jsxRuntime.jsx(TableCellWrapper, {
|
|
16197
|
+
// Стиль колонки из конфига идёт последним: шрифт задан для значений, и ширину
|
|
16198
|
+
// он не трогает — своих размеров у него нет.
|
|
16199
|
+
style: {
|
|
16200
|
+
...getColumnStyle(columnIndex, columnWidths[attribute.attributeName], attribute.resizable),
|
|
16201
|
+
...attribute.style,
|
|
16202
|
+
}, children: jsxRuntime.jsx(TableCell, { attribute: attribute, row: row, canEdit: context.canEdit, onChange: context.onCellChange }) }, attribute.attributeName))), context.canDelete && (jsxRuntime.jsx(TableActionsCell, { children: jsxRuntime.jsx(uilibGl.IconButton, { kind: "delete", title: t("actions.delete", { ns: "common", defaultValue: "Удалить" }), onClick: () => context.onRowDelete(row.key) }) }))] }, row.key))) })] }) }));
|
|
15940
16203
|
});
|
|
15941
16204
|
|
|
15942
16205
|
const TooltipIcon = styled(uilibGl.Icon).withConfig({ displayName: "TooltipIcon", componentId: "sc-1lkxudm" }) `
|
|
@@ -19405,8 +19668,6 @@ exports.GRID_DRAGGING_ATTR = GRID_DRAGGING_ATTR;
|
|
|
19405
19668
|
exports.GRID_DRAG_SOURCE_ATTR = GRID_DRAG_SOURCE_ATTR;
|
|
19406
19669
|
exports.GRID_DROP_TARGET_ATTR = GRID_DROP_TARGET_ATTR;
|
|
19407
19670
|
exports.GRID_FILL_DEFAULTS = GRID_FILL_DEFAULTS;
|
|
19408
|
-
exports.GRID_HANDLE_ATTR = GRID_HANDLE_ATTR;
|
|
19409
|
-
exports.GRID_HANDLE_PROPS = GRID_HANDLE_PROPS;
|
|
19410
19671
|
exports.GRID_ROW_ID_PREFIX = GRID_ROW_ID_PREFIX;
|
|
19411
19672
|
exports.GlobalContext = GlobalContext;
|
|
19412
19673
|
exports.GlobalProvider = GlobalProvider;
|
|
@@ -19470,6 +19731,8 @@ exports.PresentationPanelWrapper = PresentationPanelWrapper;
|
|
|
19470
19731
|
exports.PresentationWrapper = PresentationWrapper;
|
|
19471
19732
|
exports.ProgressContainer = ProgressContainer;
|
|
19472
19733
|
exports.QUERY_DESCRIPTION_CACHE_TTL = QUERY_DESCRIPTION_CACHE_TTL;
|
|
19734
|
+
exports.RESIZE_HANDLE_ATTR = RESIZE_HANDLE_ATTR;
|
|
19735
|
+
exports.ResizeHandle = ResizeHandle;
|
|
19473
19736
|
exports.RoundedBackgroundContainer = RoundedBackgroundContainer;
|
|
19474
19737
|
exports.SAVE_HOOK_RESULT_DURATION = SAVE_HOOK_RESULT_DURATION;
|
|
19475
19738
|
exports.SERVER_NOTIFICATION_EVENT = SERVER_NOTIFICATION_EVENT;
|
|
@@ -19700,6 +19963,7 @@ exports.useRelatedDataSourceAttributes = useRelatedDataSourceAttributes;
|
|
|
19700
19963
|
exports.useRemoteTask = useRemoteTask;
|
|
19701
19964
|
exports.useRenderElement = useRenderElement;
|
|
19702
19965
|
exports.useResizeBox = useResizeBox;
|
|
19966
|
+
exports.useResizeDrag = useResizeDrag;
|
|
19703
19967
|
exports.useSavePrototypeBuilder = useSavePrototypeBuilder;
|
|
19704
19968
|
exports.useServerNotificationsContext = useServerNotificationsContext;
|
|
19705
19969
|
exports.useShownOtherItems = useShownOtherItems;
|