@evergis/react 4.0.143 → 4.0.144
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/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/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 +20 -0
- package/dist/components/Dashboard/elements/ElementTable/hooks/useTableView.d.ts +7 -0
- package/dist/components/Dashboard/elements/ElementTable/styled.d.ts +38 -3
- package/dist/components/Dashboard/elements/ElementTable/types.d.ts +14 -0
- package/dist/components/Dashboard/elements/ElementTable/utils/columnStyle.d.ts +11 -0
- package/dist/components/Dashboard/types.d.ts +19 -0
- package/dist/index.js +254 -29
- package/dist/index.js.map +1 -1
- package/dist/react.esm.js +254 -29
- package/dist/react.esm.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -11407,10 +11407,17 @@ const toSchemaAttribute = (params) => {
|
|
|
11407
11407
|
attributeName,
|
|
11408
11408
|
// Тип принадлежит источнику: конфиг задаёт его только там, где источника нет.
|
|
11409
11409
|
type: source?.type ?? description?.type ?? DEFAULT_ATTRIBUTE_TYPE,
|
|
11410
|
+
// Уточнение типа — целиком за конфигом: у атрибута источника поля `subType` нет.
|
|
11411
|
+
subType: description?.subType,
|
|
11410
11412
|
alias: description?.alias ?? source?.alias ?? attributeName,
|
|
11411
11413
|
description: description?.description ?? source?.description,
|
|
11412
11414
|
isEditable: resolveIsEditable(params),
|
|
11413
11415
|
stringFormat: resolveStringFormat(description, source),
|
|
11416
|
+
// Раскладка колонки живёт только в конфиге контейнера: источник о ширине таблицы не знает.
|
|
11417
|
+
width: description?.width,
|
|
11418
|
+
resizable: description?.resizable,
|
|
11419
|
+
multiline: description?.multiline,
|
|
11420
|
+
style: description?.style,
|
|
11414
11421
|
};
|
|
11415
11422
|
};
|
|
11416
11423
|
/**
|
|
@@ -15441,6 +15448,8 @@ const TIME_FORMAT = /hh:mm/;
|
|
|
15441
15448
|
* закрытие редактора по клику снаружи обязано этот контейнер пропускать.
|
|
15442
15449
|
*/
|
|
15443
15450
|
const PORTAL_ROOT_SELECTOR = "#portal-root";
|
|
15451
|
+
/** Ниже этой ширины колонку не ужать мышью: уже неё в ячейке не остаётся места под значение. */
|
|
15452
|
+
const MIN_COLUMN_WIDTH = 48;
|
|
15444
15453
|
|
|
15445
15454
|
/**
|
|
15446
15455
|
* Собственный бокс таблицы. Без размеров в `options` представления узел ничего не меняет:
|
|
@@ -15497,10 +15506,13 @@ const TableHead = styled.thead.withConfig({ displayName: "TableHead", componentI
|
|
|
15497
15506
|
z-index: 1;
|
|
15498
15507
|
`;
|
|
15499
15508
|
const TableHeadCell = styled.th.withConfig({ displayName: "TableHeadCell", componentId: "sc-1m6xil2" }) `
|
|
15509
|
+
/* Относительное позиционирование держит захват границы колонки (TableResizeHandle). */
|
|
15510
|
+
position: relative;
|
|
15500
15511
|
padding: 0.375rem 0.5rem;
|
|
15501
15512
|
text-align: left;
|
|
15502
15513
|
font-weight: 600;
|
|
15503
15514
|
white-space: nowrap;
|
|
15515
|
+
overflow: hidden;
|
|
15504
15516
|
color: ${({ theme }) => theme.palette.textSecondary};
|
|
15505
15517
|
/* Рамка принадлежит ячейке (рамки раздельные), поэтому едет вместе с липкой шапкой. */
|
|
15506
15518
|
border-bottom: 1px solid ${({ theme }) => theme.palette.elementDeep};
|
|
@@ -15511,10 +15523,37 @@ const TableHeadCell = styled.th.withConfig({ displayName: "TableHeadCell", compo
|
|
|
15511
15523
|
user-select: none;
|
|
15512
15524
|
`}
|
|
15513
15525
|
`;
|
|
15526
|
+
/**
|
|
15527
|
+
* Содержимое заголовка. Псевдоним обрезается многоточием, значок сортировки не сжимается:
|
|
15528
|
+
* у колонки с заданной шириной длинный псевдоним иначе выдавил бы значок за край ячейки.
|
|
15529
|
+
*/
|
|
15514
15530
|
const TableHeadContent = styled.span.withConfig({ displayName: "TableHeadContent", componentId: "sc-8hzcop" }) `
|
|
15515
|
-
display:
|
|
15531
|
+
display: flex;
|
|
15516
15532
|
align-items: center;
|
|
15517
15533
|
gap: 0.25rem;
|
|
15534
|
+
min-width: 0;
|
|
15535
|
+
overflow: hidden;
|
|
15536
|
+
text-overflow: ellipsis;
|
|
15537
|
+
`;
|
|
15538
|
+
/**
|
|
15539
|
+
* Захват правой границы колонки. Лежит поверх края ячейки и ловит указатель сам, поэтому
|
|
15540
|
+
* перетаскивание не доходит до заголовка и не переключает сортировку.
|
|
15541
|
+
*
|
|
15542
|
+
* Ширина в пикселах, а не в rem: это зона попадания курсора, и от размера шрифта она не зависит.
|
|
15543
|
+
*/
|
|
15544
|
+
const TableResizeHandle = styled.span.withConfig({ displayName: "TableResizeHandle", componentId: "sc-1g0q105" }) `
|
|
15545
|
+
position: absolute;
|
|
15546
|
+
top: 0;
|
|
15547
|
+
right: 0;
|
|
15548
|
+
bottom: 0;
|
|
15549
|
+
width: 8px;
|
|
15550
|
+
cursor: col-resize;
|
|
15551
|
+
touch-action: none;
|
|
15552
|
+
user-select: none;
|
|
15553
|
+
|
|
15554
|
+
:hover {
|
|
15555
|
+
background: ${({ theme }) => theme.palette.elementDeep};
|
|
15556
|
+
}
|
|
15518
15557
|
`;
|
|
15519
15558
|
/**
|
|
15520
15559
|
* Место под значок сортировки. Занято всегда, пока сортировка включена, а сам значок только
|
|
@@ -15522,7 +15561,7 @@ const TableHeadContent = styled.span.withConfig({ displayName: "TableHeadContent
|
|
|
15522
15561
|
* клик по заголовку, вместе с телом. Оба направления рисуются значком в 1rem, поэтому
|
|
15523
15562
|
* переключение asc/des ширину тоже не трогает.
|
|
15524
15563
|
*/
|
|
15525
|
-
const TableSortSlot = styled.span.withConfig({ displayName: "TableSortSlot", componentId: "sc-
|
|
15564
|
+
const TableSortSlot = styled.span.withConfig({ displayName: "TableSortSlot", componentId: "sc-1ceykgw" }) `
|
|
15526
15565
|
display: inline-flex;
|
|
15527
15566
|
visibility: ${({ $active }) => ($active ? "visible" : "hidden")};
|
|
15528
15567
|
`;
|
|
@@ -15531,36 +15570,54 @@ const TableSortSlot = styled.span.withConfig({ displayName: "TableSortSlot", com
|
|
|
15531
15570
|
* её нижнюю кромку и сквозь прозрачную шапку не проступают. Величину пишет {@link useHeadOverlap},
|
|
15532
15571
|
* без неё отсечка нулевая и разметка не меняется.
|
|
15533
15572
|
*/
|
|
15534
|
-
const TableBody = styled.tbody.withConfig({ displayName: "TableBody", componentId: "sc-
|
|
15573
|
+
const TableBody = styled.tbody.withConfig({ displayName: "TableBody", componentId: "sc-c9drzt" }) `
|
|
15535
15574
|
clip-path: inset(var(${HEAD_OVERLAP_VARIABLE}, 0px) 0 0 0);
|
|
15536
15575
|
`;
|
|
15537
15576
|
/**
|
|
15538
15577
|
* Строка. Разделитель рисуют её ячейки: собственная рамка строки при раздельных рамках не
|
|
15539
15578
|
* рисуется вовсе, а при схлопнутых её рисовала бы таблица — и отсечка тела её бы не резала.
|
|
15540
15579
|
*/
|
|
15541
|
-
const TableRow = styled.tr.withConfig({ displayName: "TableRow", componentId: "sc-
|
|
15580
|
+
const TableRow = styled.tr.withConfig({ displayName: "TableRow", componentId: "sc-155nkv8" }) `
|
|
15542
15581
|
td {
|
|
15543
15582
|
border-bottom: 1px solid ${({ theme }) => theme.palette.elementDeep};
|
|
15544
15583
|
}
|
|
15545
15584
|
`;
|
|
15546
|
-
const TableCellWrapper = styled.td.withConfig({ displayName: "TableCellWrapper", componentId: "sc-
|
|
15585
|
+
const TableCellWrapper = styled.td.withConfig({ displayName: "TableCellWrapper", componentId: "sc-7ueth1" }) `
|
|
15547
15586
|
padding: 0.125rem 0.25rem;
|
|
15548
15587
|
vertical-align: middle;
|
|
15549
15588
|
/* Предел снимает заданная ширина: с колонками по содержимому обрезать значение незачем. */
|
|
15550
15589
|
max-width: var(--table-cell-max-width, 20rem);
|
|
15551
15590
|
`;
|
|
15552
15591
|
/** Колонка действий: узкая, не растягивается содержимым. */
|
|
15553
|
-
const TableActionsCell = styled(TableCellWrapper).withConfig({ displayName: "TableActionsCell", componentId: "sc-
|
|
15592
|
+
const TableActionsCell = styled(TableCellWrapper).withConfig({ displayName: "TableActionsCell", componentId: "sc-1m7p53b" }) `
|
|
15554
15593
|
width: 2rem;
|
|
15555
15594
|
text-align: right;
|
|
15556
15595
|
`;
|
|
15557
|
-
|
|
15596
|
+
/**
|
|
15597
|
+
* Как значение ведёт себя, когда не помещается в колонку: режется многоточием в одну строку
|
|
15598
|
+
* или переносится по словам, растягивая строку таблицы вниз (`multiline` атрибута).
|
|
15599
|
+
*
|
|
15600
|
+
* Перенос разрешаем и посреди слова: колонка бывает уже одного длинного слова (ссылка, артикул),
|
|
15601
|
+
* и без этого оно вылезло бы за заданную ширину, сделав её бессмысленной.
|
|
15602
|
+
*/
|
|
15603
|
+
const cellWrapMixin = styled.css `
|
|
15604
|
+
${({ $multiline }) => $multiline
|
|
15605
|
+
? styled.css `
|
|
15606
|
+
white-space: normal;
|
|
15607
|
+
overflow-wrap: anywhere;
|
|
15608
|
+
`
|
|
15609
|
+
: styled.css `
|
|
15610
|
+
overflow: hidden;
|
|
15611
|
+
text-overflow: ellipsis;
|
|
15612
|
+
white-space: nowrap;
|
|
15613
|
+
`}
|
|
15614
|
+
`;
|
|
15615
|
+
const CellText = styled.div.withConfig({ displayName: "CellText", componentId: "sc-19uakee" }) `
|
|
15558
15616
|
padding: 0.375rem 0.25rem;
|
|
15559
15617
|
/* Та же высота строки, что у редактора — иначе строка подпрыгивала бы по вертикали. */
|
|
15560
15618
|
line-height: 1.25rem;
|
|
15561
|
-
|
|
15562
|
-
|
|
15563
|
-
white-space: nowrap;
|
|
15619
|
+
|
|
15620
|
+
${cellWrapMixin};
|
|
15564
15621
|
`;
|
|
15565
15622
|
/**
|
|
15566
15623
|
* Редактируемая ячейка вне фокуса. Показывает значение по `stringFormat`, а редактор
|
|
@@ -15569,7 +15626,7 @@ const CellText = styled.div.withConfig({ displayName: "CellText", componentId: "
|
|
|
15569
15626
|
* Кнопка, а не просто текст, именно ради фокуса: она — таб-стоп ячейки, и Tab по таблице
|
|
15570
15627
|
* открывает редактор с курсором внутри.
|
|
15571
15628
|
*/
|
|
15572
|
-
const CellButton = styled.button.withConfig({ displayName: "CellButton", componentId: "sc-
|
|
15629
|
+
const CellButton = styled.button.withConfig({ displayName: "CellButton", componentId: "sc-16lgve4" }) `
|
|
15573
15630
|
width: 100%;
|
|
15574
15631
|
padding: 0.375rem 0.25rem;
|
|
15575
15632
|
line-height: 1.25rem;
|
|
@@ -15580,15 +15637,14 @@ const CellButton = styled.button.withConfig({ displayName: "CellButton", compone
|
|
|
15580
15637
|
color: inherit;
|
|
15581
15638
|
text-align: left;
|
|
15582
15639
|
cursor: text;
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
white-space: nowrap;
|
|
15640
|
+
|
|
15641
|
+
${cellWrapMixin};
|
|
15586
15642
|
|
|
15587
15643
|
:hover {
|
|
15588
15644
|
border-color: ${({ theme }) => theme.palette.elementDeep};
|
|
15589
15645
|
}
|
|
15590
15646
|
`;
|
|
15591
|
-
const CellPlaceholder = styled.span.withConfig({ displayName: "CellPlaceholder", componentId: "sc-
|
|
15647
|
+
const CellPlaceholder = styled.span.withConfig({ displayName: "CellPlaceholder", componentId: "sc-1jnsjad" }) `
|
|
15592
15648
|
color: ${({ theme }) => theme.palette.textSecondary};
|
|
15593
15649
|
`;
|
|
15594
15650
|
/**
|
|
@@ -15597,20 +15653,21 @@ const CellPlaceholder = styled.span.withConfig({ displayName: "CellPlaceholder",
|
|
|
15597
15653
|
* Копия остаётся в потоке и держит ширину колонки ровно такой, какой она была до клика, —
|
|
15598
15654
|
* иначе таблица дёргалась бы на каждый вход в правку и выход из неё.
|
|
15599
15655
|
*/
|
|
15600
|
-
const CellEditor = styled.div.withConfig({ displayName: "CellEditor", componentId: "sc-
|
|
15656
|
+
const CellEditor = styled.div.withConfig({ displayName: "CellEditor", componentId: "sc-ee08kh" }) `
|
|
15601
15657
|
position: relative;
|
|
15602
15658
|
min-height: 2rem;
|
|
15603
15659
|
`;
|
|
15604
|
-
const CellGhost = styled.span.withConfig({ displayName: "CellGhost", componentId: "sc-
|
|
15660
|
+
const CellGhost = styled.span.withConfig({ displayName: "CellGhost", componentId: "sc-1p10qm6" }) `
|
|
15605
15661
|
display: block;
|
|
15606
15662
|
padding: 0.375rem 0.25rem;
|
|
15607
15663
|
/* Рамка повторяет CellButton — с ней высота ячейки в правке совпадает с высотой на чтении. */
|
|
15608
15664
|
border: 1px solid transparent;
|
|
15609
15665
|
line-height: 1.25rem;
|
|
15610
|
-
white-space: nowrap;
|
|
15611
15666
|
visibility: hidden;
|
|
15667
|
+
|
|
15668
|
+
${cellWrapMixin};
|
|
15612
15669
|
`;
|
|
15613
|
-
const CellField = styled.div.withConfig({ displayName: "CellField", componentId: "sc-
|
|
15670
|
+
const CellField = styled.div.withConfig({ displayName: "CellField", componentId: "sc-cnkcp6" }) `
|
|
15614
15671
|
position: absolute;
|
|
15615
15672
|
inset: 0;
|
|
15616
15673
|
display: flex;
|
|
@@ -15625,11 +15682,97 @@ const CellField = styled.div.withConfig({ displayName: "CellField", componentId:
|
|
|
15625
15682
|
min-width: 0;
|
|
15626
15683
|
}
|
|
15627
15684
|
`;
|
|
15628
|
-
|
|
15685
|
+
/**
|
|
15686
|
+
* Ячейка со вложениями. Список идёт колонкой, кнопка добавления — под ним, поэтому строка растёт
|
|
15687
|
+
* ровно на столько, сколько файлов в ней лежит.
|
|
15688
|
+
*
|
|
15689
|
+
* Отступы те же, что у текстовой ячейки (`CellText`): в одном ряду с обычными колонками значения
|
|
15690
|
+
* обязаны стоять на одной линии.
|
|
15691
|
+
*/
|
|
15692
|
+
const AttachmentsCellBox = styled.div.withConfig({ displayName: "AttachmentsCellBox", componentId: "sc-9n5afp" }) `
|
|
15693
|
+
display: flex;
|
|
15694
|
+
flex-direction: column;
|
|
15695
|
+
align-items: flex-start;
|
|
15696
|
+
gap: 0.25rem;
|
|
15697
|
+
padding: 0.375rem 0.25rem;
|
|
15698
|
+
`;
|
|
15699
|
+
const TableEmpty = styled.div.withConfig({ displayName: "TableEmpty", componentId: "sc-1c196zb" }) `
|
|
15629
15700
|
padding: 0.75rem 0.25rem;
|
|
15630
15701
|
color: ${({ theme }) => theme.palette.textSecondary};
|
|
15631
15702
|
`;
|
|
15632
15703
|
|
|
15704
|
+
/**
|
|
15705
|
+
* Ячейка с вложениями: разбор значения, просмотр, скачивание и правка списка.
|
|
15706
|
+
*
|
|
15707
|
+
* Значение атрибута — строка: файлы приезжают в ней списком JSON, поэтому и обратно в черновик
|
|
15708
|
+
* кладём строку, а не массив — иначе тип ячейки менялся бы от самой правки.
|
|
15709
|
+
*
|
|
15710
|
+
* Добавление здесь только по ссылке. Загрузка с диска требует ресурса-родителя и файлового
|
|
15711
|
+
* api контейнера вложений, а у колонки таблицы ни того, ни другого нет.
|
|
15712
|
+
*/
|
|
15713
|
+
const useAttachmentsCell = ({ attribute, row, canEdit, onChange }) => {
|
|
15714
|
+
const [previewIndex, setPreviewIndex] = React.useState(null);
|
|
15715
|
+
const [isLinkDialogOpen, setLinkDialogOpen] = React.useState(false);
|
|
15716
|
+
const { attributeName, isEditable } = attribute;
|
|
15717
|
+
const value = row.properties[attributeName];
|
|
15718
|
+
const items = React.useMemo(() => parseAttachments(value), [value]);
|
|
15719
|
+
const previewImages = useAttachmentPreviewImages({ items, active: previewIndex !== null });
|
|
15720
|
+
const downloadByIndex = useAttachmentDownload(items);
|
|
15721
|
+
// Те же два условия, что и у обычной ячейки: режим таблицы и разрешение самого атрибута.
|
|
15722
|
+
const editable = canEdit && isEditable;
|
|
15723
|
+
const persist = React.useCallback((next) => onChange(row.key, attributeName, JSON.stringify(next)), [attributeName, onChange, row.key]);
|
|
15724
|
+
const onPreview = React.useCallback((link) => {
|
|
15725
|
+
const index = items.findIndex(item => item.link === link);
|
|
15726
|
+
if (index >= 0) {
|
|
15727
|
+
setPreviewIndex(index);
|
|
15728
|
+
}
|
|
15729
|
+
}, [items]);
|
|
15730
|
+
const onClosePreview = React.useCallback(() => setPreviewIndex(null), []);
|
|
15731
|
+
const onDownload = React.useCallback((_image, index) => downloadByIndex(index), [downloadByIndex]);
|
|
15732
|
+
const onDelete = React.useCallback((link) => persist(items.filter(item => item.link !== link)), [items, persist]);
|
|
15733
|
+
const onOpenLinkDialog = React.useCallback(() => setLinkDialogOpen(true), []);
|
|
15734
|
+
const onCloseLinkDialog = React.useCallback(() => setLinkDialogOpen(false), []);
|
|
15735
|
+
const onAddByLink = React.useCallback((url) => persist([
|
|
15736
|
+
...items,
|
|
15737
|
+
{
|
|
15738
|
+
link: url,
|
|
15739
|
+
name: getFileNameFromUrl(url),
|
|
15740
|
+
mimeType: getMimeTypeFromUrl(url),
|
|
15741
|
+
date: new Date().toISOString(),
|
|
15742
|
+
isExternal: true,
|
|
15743
|
+
},
|
|
15744
|
+
]), [items, persist]);
|
|
15745
|
+
return {
|
|
15746
|
+
items,
|
|
15747
|
+
editable,
|
|
15748
|
+
previewIndex,
|
|
15749
|
+
previewImages,
|
|
15750
|
+
isLinkDialogOpen,
|
|
15751
|
+
onPreview,
|
|
15752
|
+
onClosePreview,
|
|
15753
|
+
onDownload,
|
|
15754
|
+
onDelete,
|
|
15755
|
+
onOpenLinkDialog,
|
|
15756
|
+
onCloseLinkDialog,
|
|
15757
|
+
onAddByLink,
|
|
15758
|
+
};
|
|
15759
|
+
};
|
|
15760
|
+
|
|
15761
|
+
/**
|
|
15762
|
+
* Колонка со вложениями: список файлов прямо в ячейке.
|
|
15763
|
+
*
|
|
15764
|
+
* Тип у атрибута строковый, вложениями его делает `subType: "Attachments"` в схеме контейнера —
|
|
15765
|
+
* поэтому ветка стоит раньше разбора типа и обычных редакторов ячейки.
|
|
15766
|
+
*/
|
|
15767
|
+
const AttachmentsCell = React.memo(({ attribute, row, canEdit, onChange }) => {
|
|
15768
|
+
const { t } = useGlobalContext();
|
|
15769
|
+
const { items, editable, previewIndex, previewImages, isLinkDialogOpen, onPreview, onClosePreview, onDownload, onDelete, onOpenLinkDialog, onCloseLinkDialog, onAddByLink, } = useAttachmentsCell({ attribute, row, canEdit, onChange });
|
|
15770
|
+
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", {
|
|
15771
|
+
ns: "common",
|
|
15772
|
+
defaultValue: "Ресурс недоступен",
|
|
15773
|
+
}) }, previewIndex))] }));
|
|
15774
|
+
});
|
|
15775
|
+
|
|
15633
15776
|
/**
|
|
15634
15777
|
* Значение ячейки не изменилось.
|
|
15635
15778
|
*
|
|
@@ -15728,7 +15871,7 @@ const useCellEditing = () => {
|
|
|
15728
15871
|
const TableCell = React.memo(({ attribute, row, canEdit, onChange }) => {
|
|
15729
15872
|
const { t, language } = useGlobalContext();
|
|
15730
15873
|
const { editing, buttonProps, editorProps } = useCellEditing();
|
|
15731
|
-
const { attributeName, type, isEditable, stringFormat } = attribute;
|
|
15874
|
+
const { attributeName, type, subType, isEditable, stringFormat, multiline } = attribute;
|
|
15732
15875
|
const value = row.properties[attributeName];
|
|
15733
15876
|
const handleChange = React.useCallback((next) => {
|
|
15734
15877
|
// Пустые правки отбрасываем: иначе строка становилась бы «изменённой» от простого
|
|
@@ -15747,16 +15890,21 @@ const TableCell = React.memo(({ attribute, row, canEdit, onChange }) => {
|
|
|
15747
15890
|
const withTime = React.useMemo(() => TIME_FORMAT.test(stringFormat?.format ?? ""), [stringFormat?.format]);
|
|
15748
15891
|
// `canEdit` — режим таблицы, `isEditable` — разрешение конкретного атрибута: правка требует обоих.
|
|
15749
15892
|
const editable = canEdit && isEditable && EDITABLE_ATTRIBUTE_TYPES.includes(type);
|
|
15893
|
+
// Вложения — это строка с JSON, а не текст: разбирать и рисовать её нужно раньше любых
|
|
15894
|
+
// редакторов по типу, иначе в ячейке оказался бы сырой список файлов.
|
|
15895
|
+
if (subType === api.StringSubType.Attachments) {
|
|
15896
|
+
return jsxRuntime.jsx(AttachmentsCell, { attribute: attribute, row: row, canEdit: canEdit, onChange: onChange });
|
|
15897
|
+
}
|
|
15750
15898
|
// Логическое значение и на чтение показываем галкой, а не словами «true»/«false» —
|
|
15751
15899
|
// так же, как остальной дашборд рисует булевы атрибуты.
|
|
15752
15900
|
if (type === api.AttributeType.Boolean) {
|
|
15753
15901
|
return jsxRuntime.jsx(uilibGl.Checkbox, { checked: !!value, disabled: !editable, onChange: () => handleChange(!value) });
|
|
15754
15902
|
}
|
|
15755
15903
|
if (!editable) {
|
|
15756
|
-
return jsxRuntime.jsx(CellText, { title: formatted, children: formatted || jsxRuntime.jsx(CellPlaceholder, { children: "\u2014" }) });
|
|
15904
|
+
return (jsxRuntime.jsx(CellText, { "$multiline": multiline, title: formatted, children: formatted || jsxRuntime.jsx(CellPlaceholder, { children: "\u2014" }) }));
|
|
15757
15905
|
}
|
|
15758
15906
|
if (!editing) {
|
|
15759
|
-
return (jsxRuntime.jsx(CellButton, { type: "button", ...buttonProps, children: formatted || jsxRuntime.jsx(CellPlaceholder, { children: "\u2014" }) }));
|
|
15907
|
+
return (jsxRuntime.jsx(CellButton, { type: "button", "$multiline": multiline, ...buttonProps, children: formatted || jsxRuntime.jsx(CellPlaceholder, { children: "\u2014" }) }));
|
|
15760
15908
|
}
|
|
15761
15909
|
const renderEditor = () => {
|
|
15762
15910
|
if (type === api.AttributeType.DateTime) {
|
|
@@ -15767,9 +15915,31 @@ const TableCell = React.memo(({ attribute, row, canEdit, onChange }) => {
|
|
|
15767
15915
|
}
|
|
15768
15916
|
return (jsxRuntime.jsx(uilibGl.Input, { autoFocus: true, value: value === null || value === undefined ? "" : String(value), width: "100%", onChange: ({ target }) => handleChange(target.value) }));
|
|
15769
15917
|
};
|
|
15770
|
-
return (jsxRuntime.jsxs(CellEditor, { ...editorProps, children: [jsxRuntime.jsx(CellGhost, { children: formatted || "—" }), jsxRuntime.jsx(CellField, { children: renderEditor() })] }));
|
|
15918
|
+
return (jsxRuntime.jsxs(CellEditor, { ...editorProps, children: [jsxRuntime.jsx(CellGhost, { "$multiline": multiline, children: formatted || "—" }), jsxRuntime.jsx(CellField, { children: renderEditor() })] }));
|
|
15771
15919
|
});
|
|
15772
15920
|
|
|
15921
|
+
/**
|
|
15922
|
+
* Инлайн-размер ячейки колонки с заданной шириной.
|
|
15923
|
+
*
|
|
15924
|
+
* Раскладка таблицы автоматическая (ширину колонок выбирает содержимое), и одной `width` ей мало —
|
|
15925
|
+
* она для неё лишь пожелание. Жёстко колонку держат все три предела сразу, поэтому и ставим их
|
|
15926
|
+
* втроём, на каждую ячейку колонки.
|
|
15927
|
+
*
|
|
15928
|
+
* Ширины нет — стиля нет: колонка остаётся на попечении раскладки, как и была.
|
|
15929
|
+
*/
|
|
15930
|
+
const getColumnStyle = (width) => width == null ? undefined : { width, minWidth: width, maxWidth: width };
|
|
15931
|
+
|
|
15932
|
+
/**
|
|
15933
|
+
* Шапка таблицы: заголовки колонок, сортировка кликом и захват границы для ручной ширины.
|
|
15934
|
+
*
|
|
15935
|
+
* Захват рисуется по разрешению атрибута (`resizable`) и не смотрит на режим правки контейнера:
|
|
15936
|
+
* ширина колонки — это вид, а не данные.
|
|
15937
|
+
*/
|
|
15938
|
+
const TableHeadRow = ({ columns, sort, sortEnabled, columnWidths, withActionsColumn, onSortToggle, onResizeStart, onResizeMove, onResizeEnd, }) => (jsxRuntime.jsxs("tr", { children: [columns.map(({ attributeName, alias, description, resizable }) => {
|
|
15939
|
+
const sorted = sort?.attributeName === attributeName;
|
|
15940
|
+
return (jsxRuntime.jsxs(TableHeadCell, { title: description || alias, style: getColumnStyle(columnWidths[attributeName]), "$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(TableResizeHandle, { onPointerDown: event => onResizeStart(attributeName, event), onPointerMove: onResizeMove, onPointerUp: onResizeEnd, onPointerCancel: onResizeEnd }))] }, attributeName));
|
|
15941
|
+
}), withActionsColumn && jsxRuntime.jsx(TableHeadCell, {})] }));
|
|
15942
|
+
|
|
15773
15943
|
const ContainerLoading = () => (jsxRuntime.jsx(uilibGl.Flex, { alignContent: "center", justifyContent: "center", width: "100%", children: jsxRuntime.jsx(uilibGl.CircularProgress, { diameter: 1.5, mono: true }) }));
|
|
15774
15944
|
|
|
15775
15945
|
/**
|
|
@@ -15873,6 +16043,53 @@ const getTableBoxStyle = (width, height) => {
|
|
|
15873
16043
|
};
|
|
15874
16044
|
};
|
|
15875
16045
|
|
|
16046
|
+
/**
|
|
16047
|
+
* Ручное изменение ширины колонок.
|
|
16048
|
+
*
|
|
16049
|
+
* Растянутые ширины живут только здесь: тянут их в рантайме, а `width` атрибута в конфиге задаёт
|
|
16050
|
+
* лишь стартовое значение — записывать растянутое обратно в конфиг незачем.
|
|
16051
|
+
*
|
|
16052
|
+
* Разрешение (`resizable`) от `options.editMode` не зависит: ширина колонки — это вид таблицы,
|
|
16053
|
+
* а не правка данных, и тянуть её должно быть можно и на таблице только для чтения.
|
|
16054
|
+
*
|
|
16055
|
+
* Стартовую ширину берём с самой ячейки, а не из конфига: у колонки без `width` её выбрала
|
|
16056
|
+
* раскладка таблицы, и тянуть надо ровно от того, что видно.
|
|
16057
|
+
*/
|
|
16058
|
+
const useColumnResize = (columns) => {
|
|
16059
|
+
const [widths, setWidths] = React.useState({});
|
|
16060
|
+
const dragRef = React.useRef(null);
|
|
16061
|
+
const onResizeStart = React.useCallback((attributeName, event) => {
|
|
16062
|
+
const handle = event.currentTarget;
|
|
16063
|
+
// Захват границы — не клик по заголовку: иначе каждое перетаскивание переключало бы сортировку.
|
|
16064
|
+
event.preventDefault();
|
|
16065
|
+
event.stopPropagation();
|
|
16066
|
+
dragRef.current = {
|
|
16067
|
+
attributeName,
|
|
16068
|
+
startX: event.clientX,
|
|
16069
|
+
startWidth: handle.closest("th")?.getBoundingClientRect().width ?? MIN_COLUMN_WIDTH,
|
|
16070
|
+
};
|
|
16071
|
+
handle.setPointerCapture(event.pointerId);
|
|
16072
|
+
}, []);
|
|
16073
|
+
const onResizeMove = React.useCallback((event) => {
|
|
16074
|
+
const drag = dragRef.current;
|
|
16075
|
+
if (!drag) {
|
|
16076
|
+
return;
|
|
16077
|
+
}
|
|
16078
|
+
const next = Math.max(MIN_COLUMN_WIDTH, Math.round(drag.startWidth + event.clientX - drag.startX));
|
|
16079
|
+
setWidths(current => current[drag.attributeName] === next ? current : { ...current, [drag.attributeName]: next });
|
|
16080
|
+
}, []);
|
|
16081
|
+
const onResizeEnd = React.useCallback((event) => {
|
|
16082
|
+
const handle = event.currentTarget;
|
|
16083
|
+
dragRef.current = null;
|
|
16084
|
+
if (handle.hasPointerCapture(event.pointerId)) {
|
|
16085
|
+
handle.releasePointerCapture(event.pointerId);
|
|
16086
|
+
}
|
|
16087
|
+
}, []);
|
|
16088
|
+
// Растянутая ширина перебивает конфигурационную: последнее слово за тем, кто тянул.
|
|
16089
|
+
const columnWidths = React.useMemo(() => columns.reduce((acc, { attributeName, width }) => ({ ...acc, [attributeName]: widths[attributeName] ?? width }), {}), [columns, widths]);
|
|
16090
|
+
return { columnWidths, onResizeStart, onResizeMove, onResizeEnd };
|
|
16091
|
+
};
|
|
16092
|
+
|
|
15876
16093
|
/**
|
|
15877
16094
|
* Вид таблицы: колонки плюс локальная сортировка.
|
|
15878
16095
|
*
|
|
@@ -15885,6 +16102,9 @@ const getTableBoxStyle = (width, height) => {
|
|
|
15885
16102
|
*
|
|
15886
16103
|
* `width`/`height` — размер собственного бокса таблицы: он ограничивает её саму и прокручивает
|
|
15887
16104
|
* то, что не влезло. Место, отведённое контейнеру, задаётся его же опциями и живёт отдельно.
|
|
16105
|
+
*
|
|
16106
|
+
* Ширины колонок — тоже вид: конфиг задаёт стартовую, растянутая мышью живёт в
|
|
16107
|
+
* {@link useColumnResize} и в конфиг не возвращается.
|
|
15888
16108
|
*/
|
|
15889
16109
|
const useTableView = (elementConfig) => {
|
|
15890
16110
|
const context = React.useContext(StructuredDataContext);
|
|
@@ -15895,6 +16115,7 @@ const useTableView = (elementConfig) => {
|
|
|
15895
16115
|
const columns = React.useMemo(() => schema ?? [], [schema]);
|
|
15896
16116
|
const sortedRows = React.useMemo(() => sortRows(rows ?? [], sort, schema ?? []), [rows, schema, sort]);
|
|
15897
16117
|
const sizeCss = React.useMemo(() => getTableBoxStyle(width, height), [height, width]);
|
|
16118
|
+
const { columnWidths, onResizeStart, onResizeMove, onResizeEnd } = useColumnResize(columns);
|
|
15898
16119
|
const onSortToggle = React.useCallback((attributeName) => setSort(current => getNextSort(current, attributeName)), []);
|
|
15899
16120
|
return {
|
|
15900
16121
|
context,
|
|
@@ -15905,7 +16126,11 @@ const useTableView = (elementConfig) => {
|
|
|
15905
16126
|
sizeCss,
|
|
15906
16127
|
// Ширина задана — колонки по содержимому: лишнее уходит в прокрутку бокса, а не в многоточие.
|
|
15907
16128
|
contentWidth: width != null,
|
|
16129
|
+
columnWidths,
|
|
15908
16130
|
onSortToggle,
|
|
16131
|
+
onResizeStart,
|
|
16132
|
+
onResizeMove,
|
|
16133
|
+
onResizeEnd,
|
|
15909
16134
|
};
|
|
15910
16135
|
};
|
|
15911
16136
|
|
|
@@ -15917,7 +16142,7 @@ const useTableView = (elementConfig) => {
|
|
|
15917
16142
|
*/
|
|
15918
16143
|
const ElementTable = React.memo(({ elementConfig }) => {
|
|
15919
16144
|
const { t } = useGlobalContext();
|
|
15920
|
-
const { context, columns, rows, sort, sortEnabled, sizeCss, contentWidth, onSortToggle } = useTableView(elementConfig);
|
|
16145
|
+
const { context, columns, rows, sort, sortEnabled, sizeCss, contentWidth, columnWidths, onSortToggle, onResizeStart, onResizeMove, onResizeEnd, } = useTableView(elementConfig);
|
|
15921
16146
|
// Узел таблицы держим состоянием, а не ref: отсечка должна встать сразу, как он появится.
|
|
15922
16147
|
const [table, setTable] = React.useState(null);
|
|
15923
16148
|
useHeadOverlap(table);
|
|
@@ -15933,10 +16158,10 @@ const ElementTable = React.memo(({ elementConfig }) => {
|
|
|
15933
16158
|
defaultValue: "Схема данных не задана",
|
|
15934
16159
|
}) }));
|
|
15935
16160
|
}
|
|
15936
|
-
return (jsxRuntime.jsx(TableBox, { "$sizeCss": sizeCss, children: jsxRuntime.jsxs(TableWrapper, { ref: setTable, "$contentWidth": contentWidth, children: [jsxRuntime.jsx(TableHead, { children: jsxRuntime.
|
|
15937
|
-
|
|
15938
|
-
|
|
15939
|
-
|
|
16161
|
+
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, onResizeStart: onResizeStart, onResizeMove: onResizeMove, onResizeEnd: onResizeEnd }) }), jsxRuntime.jsx(TableBody, { children: rows.map(row => (jsxRuntime.jsxs(TableRow, { children: [columns.map(attribute => (jsxRuntime.jsx(TableCellWrapper, {
|
|
16162
|
+
// Стиль колонки из конфига идёт последним: шрифт задан для значений, и ширину
|
|
16163
|
+
// он не трогает — своих размеров у него нет.
|
|
16164
|
+
style: { ...getColumnStyle(columnWidths[attribute.attributeName]), ...attribute.style }, 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
16165
|
});
|
|
15941
16166
|
|
|
15942
16167
|
const TooltipIcon = styled(uilibGl.Icon).withConfig({ displayName: "TooltipIcon", componentId: "sc-1lkxudm" }) `
|