@evergis/react 4.0.156 → 4.0.158
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/LayersContainer/styled.d.ts +0 -2
- package/dist/components/Dashboard/containers/StructuredDataContainer/constants.d.ts +3 -0
- package/dist/components/Dashboard/containers/StructuredDataContainer/hooks/useStructuredDataSchema.d.ts +4 -0
- package/dist/components/Dashboard/containers/StructuredDataContainer/types.d.ts +8 -1
- package/dist/components/Dashboard/containers/StructuredDataContainer/utils/listOptions.d.ts +17 -0
- package/dist/components/Dashboard/elements/ElementModal/styled.d.ts +6 -0
- package/dist/components/Dashboard/elements/ElementTable/components/ChipsCell.d.ts +13 -0
- package/dist/components/Dashboard/elements/ElementTable/components/DropdownCell.d.ts +10 -0
- package/dist/components/Dashboard/elements/ElementTable/constants.d.ts +9 -0
- package/dist/components/Dashboard/elements/ElementTable/hooks/useCellEditing.d.ts +1 -0
- package/dist/components/Dashboard/elements/ElementTable/hooks/useDropdownAutoOpen.d.ts +9 -0
- package/dist/components/Dashboard/elements/ElementTable/styled.d.ts +11 -0
- package/dist/components/Dashboard/elements/ElementTable/utils/listValue.d.ts +11 -0
- package/dist/components/Dashboard/styled/dashboard.d.ts +0 -1
- package/dist/components/Dashboard/types.d.ts +21 -0
- package/dist/index.js +367 -164
- package/dist/index.js.map +1 -1
- package/dist/react.esm.js +367 -164
- package/dist/react.esm.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -5294,8 +5294,7 @@ const DashboardWrapper = styled(uilibGl.Flex).withConfig({ displayName: "Dashboa
|
|
|
5294
5294
|
width: calc(100% - 3rem);
|
|
5295
5295
|
height: calc(100% - ${LEFT_PANEL_HEADER_HEIGHT});
|
|
5296
5296
|
|
|
5297
|
-
${({ hasImage
|
|
5298
|
-
!isPresentationMode &&
|
|
5297
|
+
${({ hasImage }) => hasImage &&
|
|
5299
5298
|
styled.css `
|
|
5300
5299
|
margin-top: -0.35rem;
|
|
5301
5300
|
`}
|
|
@@ -5905,7 +5904,7 @@ const useAttachmentDownload = (items) => {
|
|
|
5905
5904
|
return;
|
|
5906
5905
|
inFlightRef.current.add(item.link);
|
|
5907
5906
|
api.catalog
|
|
5908
|
-
.getFile(item.link)
|
|
5907
|
+
.getFile({ resourceId: item.link })
|
|
5909
5908
|
.then(blob => saveBlobAsFile(blob, item.name))
|
|
5910
5909
|
.catch(error => {
|
|
5911
5910
|
notification?.add({
|
|
@@ -6188,7 +6187,7 @@ const useAttachmentPreviewImages = ({ items, active, }) => {
|
|
|
6188
6187
|
return;
|
|
6189
6188
|
inFlightRef.current.add(item.link);
|
|
6190
6189
|
api.catalog
|
|
6191
|
-
.getFile(item.link)
|
|
6190
|
+
.getFile({ resourceId: item.link })
|
|
6192
6191
|
.then(blob => {
|
|
6193
6192
|
const objectUrl = URL.createObjectURL(blob);
|
|
6194
6193
|
setBlobUrls(prev => ({ ...prev, [item.link]: objectUrl }));
|
|
@@ -6575,7 +6574,7 @@ const useMapImages = ({ images }) => {
|
|
|
6575
6574
|
// Картинка из каталога
|
|
6576
6575
|
if (!config.url.startsWith("data:image") && !config.url.startsWith("http")) {
|
|
6577
6576
|
try {
|
|
6578
|
-
const { data } = await loadSvgAsImage(URL.createObjectURL(await api.catalog.getFile(config.url)), size, pixelRatio);
|
|
6577
|
+
const { data } = await loadSvgAsImage(URL.createObjectURL(await api.catalog.getFile({ resourceId: config.url })), size, pixelRatio);
|
|
6579
6578
|
map.current.addImage(config.name, data, {
|
|
6580
6579
|
sdf,
|
|
6581
6580
|
pixelRatio,
|
|
@@ -9128,7 +9127,7 @@ const FileImagePreview = ({ link, isExternal, size, borderRadius, }) => {
|
|
|
9128
9127
|
let cancelled = false;
|
|
9129
9128
|
let objectUrl;
|
|
9130
9129
|
api.catalog
|
|
9131
|
-
.getFile(link)
|
|
9130
|
+
.getFile({ resourceId: link })
|
|
9132
9131
|
.then(blob => {
|
|
9133
9132
|
if (cancelled)
|
|
9134
9133
|
return;
|
|
@@ -11144,7 +11143,7 @@ const LayersContainerWrapper = styled(Container).withConfig({ displayName: "Laye
|
|
|
11144
11143
|
}
|
|
11145
11144
|
|
|
11146
11145
|
${LayerGroupList} {
|
|
11147
|
-
margin:
|
|
11146
|
+
margin: 0 -1.25rem 0 -1rem;
|
|
11148
11147
|
padding: 0;
|
|
11149
11148
|
}
|
|
11150
11149
|
`;
|
|
@@ -11735,6 +11734,8 @@ const NUMBER_EDITOR_TYPES = [
|
|
|
11735
11734
|
const INTEGER_ATTRIBUTE_TYPES = [api.AttributeType.Int32, api.AttributeType.Int64];
|
|
11736
11735
|
/** Тип атрибута, когда его не задали ни конфиг, ни источник. */
|
|
11737
11736
|
const DEFAULT_ATTRIBUTE_TYPE = api.AttributeType.String;
|
|
11737
|
+
/** Контрол колонки со списком значений, когда `control` в описании атрибута не задан. */
|
|
11738
|
+
const DEFAULT_ATTRIBUTE_CONTROL = "dropdown";
|
|
11738
11739
|
/** Сколько миллисекунд висит нотификация об ошибке записи. */
|
|
11739
11740
|
const SAVE_ERROR_DURATION = 5000;
|
|
11740
11741
|
|
|
@@ -12014,6 +12015,53 @@ const buildStructuredDataSchema = ({ attributesDescription, attributesConfigurat
|
|
|
12014
12015
|
return sourceAttributes.map(source => toSchemaAttribute({ source, idAttribute, canWriteToSource }));
|
|
12015
12016
|
};
|
|
12016
12017
|
|
|
12018
|
+
/**
|
|
12019
|
+
* Записи связанного источника → пункты списка.
|
|
12020
|
+
*
|
|
12021
|
+
* Пустые значения отбрасываются: выбрать «ничего» можно и без них, а пункт с пустым значением
|
|
12022
|
+
* стёр бы ячейку под видом осмысленного выбора. Повторы схлопываются — у справочника по
|
|
12023
|
+
* значению одна запись, а у сырого слоя их может быть сколько угодно.
|
|
12024
|
+
*/
|
|
12025
|
+
const featuresToListOptions = (features, valueAttribute, textAttribute) => lodash.uniqBy(features
|
|
12026
|
+
.filter(({ properties }) => !lodash.isNil(properties?.[valueAttribute]))
|
|
12027
|
+
.map(({ properties }) => ({
|
|
12028
|
+
value: properties[valueAttribute],
|
|
12029
|
+
text: String(properties[textAttribute] ?? properties[valueAttribute]),
|
|
12030
|
+
})), ({ value }) => String(value));
|
|
12031
|
+
/**
|
|
12032
|
+
* Список допустимых значений колонки: варианты из конфига, а без них — записи связанного
|
|
12033
|
+
* источника. `undefined` — списка нет, и значение вводится вручную.
|
|
12034
|
+
*
|
|
12035
|
+
* Источник, ещё не загруженный или не найденный, даёт пустой список, а не его отсутствие:
|
|
12036
|
+
* колонка со списком не должна на время загрузки превращаться в свободный ввод.
|
|
12037
|
+
*/
|
|
12038
|
+
const getAttributeListOptions = (description, dataSources) => {
|
|
12039
|
+
const { attributeName, variants, relatedDataSource, attributeValue, attributeAlias } = description || {};
|
|
12040
|
+
if (variants?.length) {
|
|
12041
|
+
return variants;
|
|
12042
|
+
}
|
|
12043
|
+
if (!relatedDataSource) {
|
|
12044
|
+
return undefined;
|
|
12045
|
+
}
|
|
12046
|
+
const valueAttribute = attributeValue || attributeName;
|
|
12047
|
+
const features = getDataSource(relatedDataSource, dataSources)?.features ?? [];
|
|
12048
|
+
return featuresToListOptions(features, valueAttribute, attributeAlias || valueAttribute);
|
|
12049
|
+
};
|
|
12050
|
+
/**
|
|
12051
|
+
* Схема представления: схема данных плюс списки значений и их контролы.
|
|
12052
|
+
*
|
|
12053
|
+
* Отдельно от схемы данных потому, что зависит от загруженных источников страницы: черновик
|
|
12054
|
+
* пересобирает строки при каждой смене схемы, и загрузка справочника стирала бы в нём правки.
|
|
12055
|
+
*/
|
|
12056
|
+
const withListControls = (schema, attributesDescription, dataSources) => schema.map(attribute => {
|
|
12057
|
+
const description = attributesDescription?.find(({ attributeName }) => attributeName === attribute.attributeName);
|
|
12058
|
+
const listOptions = getAttributeListOptions(description, dataSources);
|
|
12059
|
+
if (!listOptions) {
|
|
12060
|
+
return attribute;
|
|
12061
|
+
}
|
|
12062
|
+
return { ...attribute, listOptions, control: description?.control ?? DEFAULT_ATTRIBUTE_CONTROL };
|
|
12063
|
+
});
|
|
12064
|
+
|
|
12017
12065
|
/**
|
|
12018
12066
|
* Схема структурированных данных и источник, к которому она привязана.
|
|
12019
12067
|
*
|
|
@@ -12026,6 +12074,9 @@ const buildStructuredDataSchema = ({ attributesDescription, attributesConfigurat
|
|
|
12026
12074
|
*
|
|
12027
12075
|
* Берётся из конфига страницы, а не из загруженного источника: от него зависит, какие ячейки
|
|
12028
12076
|
* редактируемы, и до окончания загрузки схема не должна меняться.
|
|
12077
|
+
*
|
|
12078
|
+
* `viewSchema` — та же схема со списками значений для представления. Списки зависят от
|
|
12079
|
+
* загруженных источников страницы, поэтому в схему данных (черновик, запись) они не входят.
|
|
12029
12080
|
*/
|
|
12030
12081
|
const useStructuredDataSchema = (type, elementConfig) => {
|
|
12031
12082
|
const { dataSources } = useWidgetContext(type);
|
|
@@ -12038,7 +12089,8 @@ const useStructuredDataSchema = (type, elementConfig) => {
|
|
|
12038
12089
|
const layerName = React.useMemo(() => (saveToLayer ? configDataSource?.layerName : undefined), [configDataSource?.layerName, saveToLayer]);
|
|
12039
12090
|
const attributesConfiguration = React.useMemo(() => (relatedDataSource ? getAttributesConfiguration(layerInfo) : undefined), [layerInfo, relatedDataSource]);
|
|
12040
12091
|
const schema = React.useMemo(() => buildStructuredDataSchema({ attributesDescription, attributesConfiguration, canWriteToSource: !!layerName }), [attributesDescription, attributesConfiguration, layerName]);
|
|
12041
|
-
|
|
12092
|
+
const viewSchema = React.useMemo(() => withListControls(schema, attributesDescription, dataSources), [attributesDescription, dataSources, schema]);
|
|
12093
|
+
return { schema, viewSchema, dataSource, layerName };
|
|
12042
12094
|
};
|
|
12043
12095
|
|
|
12044
12096
|
/**
|
|
@@ -12060,7 +12112,7 @@ const useStructuredData = (type, elementConfig) => {
|
|
|
12060
12112
|
const { filters } = useWidgetContext(type);
|
|
12061
12113
|
const { currentPage } = useWidgetPage(type);
|
|
12062
12114
|
const { relatedDataSource, filterName, editMode } = elementConfig?.options || {};
|
|
12063
|
-
const { schema, dataSource, layerName } = useStructuredDataSchema(type, elementConfig);
|
|
12115
|
+
const { schema, viewSchema, dataSource, layerName } = useStructuredDataSchema(type, elementConfig);
|
|
12064
12116
|
const hasDataSource = !!relatedDataSource;
|
|
12065
12117
|
const configFilter = React.useMemo(() => getConfigFilter(filterName, currentPage?.filters), [currentPage?.filters, filterName]);
|
|
12066
12118
|
const filterValue = React.useMemo(() => (filterName ? filters?.[filterName]?.value ?? configFilter?.defaultValue : undefined), [configFilter?.defaultValue, filterName, filters]);
|
|
@@ -12102,14 +12154,14 @@ const useStructuredData = (type, elementConfig) => {
|
|
|
12102
12154
|
setDeletingKey(null);
|
|
12103
12155
|
}, [deleteRow, deletingKey]);
|
|
12104
12156
|
const contextValue = React.useMemo(() => ({
|
|
12105
|
-
schema,
|
|
12157
|
+
schema: viewSchema,
|
|
12106
12158
|
rows: visibleRows,
|
|
12107
12159
|
loading,
|
|
12108
12160
|
canEdit,
|
|
12109
12161
|
canDelete: canChangeRows,
|
|
12110
12162
|
onCellChange: changeCell,
|
|
12111
12163
|
onRowDelete,
|
|
12112
|
-
}), [canChangeRows, canEdit, changeCell, loading, onRowDelete,
|
|
12164
|
+
}), [canChangeRows, canEdit, changeCell, loading, onRowDelete, viewSchema, visibleRows]);
|
|
12113
12165
|
return {
|
|
12114
12166
|
contextValue,
|
|
12115
12167
|
dirty,
|
|
@@ -16170,6 +16222,12 @@ const ATTACHMENTS_INLINE_LIMIT = 1;
|
|
|
16170
16222
|
* Величина с макета и опцией не выносится: поп-ап у колонки один на все таблицы.
|
|
16171
16223
|
*/
|
|
16172
16224
|
const ATTACHMENTS_SHOWN_ITEMS = 3;
|
|
16225
|
+
/**
|
|
16226
|
+
* Пустой пункт выпадающего списка в ячейке: выбор снимает значение. У `Dropdown` пункт с пустым
|
|
16227
|
+
* значением показывается как плейсхолдер, поэтому и подпись у него — тот же прочерк, что у
|
|
16228
|
+
* пустой ячейки.
|
|
16229
|
+
*/
|
|
16230
|
+
const EMPTY_LIST_OPTION = { text: "—", value: "" };
|
|
16173
16231
|
/** Вид списка, с которого поп-ап вложений открывается. */
|
|
16174
16232
|
const ATTACHMENTS_VIEW_MODE = "list";
|
|
16175
16233
|
|
|
@@ -16463,7 +16521,27 @@ const ColorSwatch = styled.div.withConfig({ displayName: "ColorSwatch", componen
|
|
|
16463
16521
|
background-color: ${({ $color }) => $color};
|
|
16464
16522
|
border-radius: ${({ theme: { borderRadius } }) => borderRadius.tiny};
|
|
16465
16523
|
`;
|
|
16466
|
-
|
|
16524
|
+
/**
|
|
16525
|
+
* Ряд кнопок колонки со списком. Не влезает в ширину колонки — переносится, и строка таблицы
|
|
16526
|
+
* растёт вниз: резать выбор многоточием, как текст, нельзя.
|
|
16527
|
+
*
|
|
16528
|
+
* Зазор между кнопками по горизонтали даёт их собственный правый отступ, поэтому здесь задан
|
|
16529
|
+
* только межстрочный. Отступы ряда — как у текстовой ячейки, чтобы значения соседних колонок
|
|
16530
|
+
* стояли на одной линии.
|
|
16531
|
+
*/
|
|
16532
|
+
const ChipsCellBox = styled.div.withConfig({ displayName: "ChipsCellBox", componentId: "sc-1qxeyw" }) `
|
|
16533
|
+
display: flex;
|
|
16534
|
+
flex-wrap: wrap;
|
|
16535
|
+
align-items: center;
|
|
16536
|
+
row-gap: 0.25rem;
|
|
16537
|
+
padding: 0.25rem;
|
|
16538
|
+
|
|
16539
|
+
/* Удвоенный класс перебивает собственный указатель кнопки-чипа. */
|
|
16540
|
+
&& > * {
|
|
16541
|
+
cursor: ${({ $interactive }) => ($interactive ? "pointer" : "default")};
|
|
16542
|
+
}
|
|
16543
|
+
`;
|
|
16544
|
+
const TableEmpty = styled.div.withConfig({ displayName: "TableEmpty", componentId: "sc-jwp9aw" }) `
|
|
16467
16545
|
padding: 0.75rem 0.25rem;
|
|
16468
16546
|
color: ${({ theme }) => theme.palette.textSecondary};
|
|
16469
16547
|
`;
|
|
@@ -16651,6 +16729,175 @@ const AttachmentsCell = React.memo(({ attribute, row, canEdit, onChange }) => {
|
|
|
16651
16729
|
}) }, state.previewIndex))] }));
|
|
16652
16730
|
});
|
|
16653
16731
|
|
|
16732
|
+
const FilterChip = styled.div.withConfig({ displayName: "FilterChip", componentId: "sc-17fod1d" }) `
|
|
16733
|
+
display: inline-flex;
|
|
16734
|
+
align-items: center;
|
|
16735
|
+
gap: 0.25rem;
|
|
16736
|
+
padding: 0.3125rem 0.5rem;
|
|
16737
|
+
height: 1.5rem;
|
|
16738
|
+
border-radius: 0.25rem;
|
|
16739
|
+
background-color: ${({ $isActive, $bgColor, theme }) => $bgColor
|
|
16740
|
+
? $bgColor
|
|
16741
|
+
: $isActive
|
|
16742
|
+
? theme.palette?.primary
|
|
16743
|
+
: theme.palette?.elementLight};
|
|
16744
|
+
color: ${({ $isActive, $textColor, theme }) => $textColor
|
|
16745
|
+
? $textColor
|
|
16746
|
+
: $isActive
|
|
16747
|
+
? theme.palette?.textContrast
|
|
16748
|
+
: theme.palette?.textSecondary};
|
|
16749
|
+
cursor: pointer;
|
|
16750
|
+
font-size: 0.75rem;
|
|
16751
|
+
line-height: 0.875rem;
|
|
16752
|
+
white-space: nowrap;
|
|
16753
|
+
flex-shrink: 0;
|
|
16754
|
+
transition: all 0.2s ease-in-out;
|
|
16755
|
+
margin: 0 0.25rem 0 0;
|
|
16756
|
+
box-sizing: border-box;
|
|
16757
|
+
|
|
16758
|
+
&:hover {
|
|
16759
|
+
background-color: ${({ $isActive, $bgColor, theme }) => $isActive
|
|
16760
|
+
? $bgColor || 'inherit'
|
|
16761
|
+
: $bgColor || theme.palette?.elementDark};
|
|
16762
|
+
}
|
|
16763
|
+
`;
|
|
16764
|
+
const ChipIconWrapper = styled.span.withConfig({ displayName: "ChipIconWrapper", componentId: "sc-8j3rkq" }) `
|
|
16765
|
+
display: inline-flex;
|
|
16766
|
+
align-items: center;
|
|
16767
|
+
justify-content: center;
|
|
16768
|
+
width: 0.875rem;
|
|
16769
|
+
height: 0.875rem;
|
|
16770
|
+
flex-shrink: 0;
|
|
16771
|
+
|
|
16772
|
+
svg, img {
|
|
16773
|
+
width: 100%;
|
|
16774
|
+
height: 100%;
|
|
16775
|
+
display: block;
|
|
16776
|
+
}
|
|
16777
|
+
`;
|
|
16778
|
+
const ChipText = styled.span.withConfig({ displayName: "ChipText", componentId: "sc-1saygd0" }) `
|
|
16779
|
+
overflow: hidden;
|
|
16780
|
+
text-overflow: ellipsis;
|
|
16781
|
+
white-space: nowrap;
|
|
16782
|
+
${({ $maxTextWidth }) => $maxTextWidth && `max-width: ${$maxTextWidth / 16}rem;`}
|
|
16783
|
+
`;
|
|
16784
|
+
|
|
16785
|
+
const CustomChip = ({ text, icon, color, primary, secondary, error, disabled, isActive, maxTextWidth, fontColor: customFontColor, backgroundColor: customBackgroundColor, ...props }) => {
|
|
16786
|
+
const theme = styled.useTheme();
|
|
16787
|
+
const { palette } = theme;
|
|
16788
|
+
// Helper to add transparency to hex color
|
|
16789
|
+
const addTransparency = (hex, transparency = '1f') => {
|
|
16790
|
+
// Ensure hex starts with #
|
|
16791
|
+
const cleanHex = hex.startsWith('#') ? hex : `#${hex}`;
|
|
16792
|
+
return `${cleanHex}${transparency}`;
|
|
16793
|
+
};
|
|
16794
|
+
// Calculate colors based on priority:
|
|
16795
|
+
// 1. customFontColor / customBackgroundColor (highest, from options)
|
|
16796
|
+
// 2. color (from attribute)
|
|
16797
|
+
// 3. primary/secondary/error
|
|
16798
|
+
// 4. default theme colors
|
|
16799
|
+
let bgColor;
|
|
16800
|
+
let textColor;
|
|
16801
|
+
if (isActive) {
|
|
16802
|
+
// Priority 1: Custom colors from options
|
|
16803
|
+
if (customFontColor) {
|
|
16804
|
+
textColor = customFontColor;
|
|
16805
|
+
bgColor = customBackgroundColor || addTransparency(customFontColor); // 12% opacity
|
|
16806
|
+
}
|
|
16807
|
+
// Priority 2: Color from attribute
|
|
16808
|
+
else if (color) {
|
|
16809
|
+
textColor = palette.textPrimary; // White text for active chip
|
|
16810
|
+
bgColor = color; // Full color without transparency
|
|
16811
|
+
}
|
|
16812
|
+
// Priority 3: Variant colors
|
|
16813
|
+
else if (primary) {
|
|
16814
|
+
bgColor = theme.message.color.success; // Green
|
|
16815
|
+
textColor = palette.textContrast;
|
|
16816
|
+
}
|
|
16817
|
+
else if (secondary) {
|
|
16818
|
+
bgColor = palette.primary;
|
|
16819
|
+
textColor = palette.textContrast;
|
|
16820
|
+
}
|
|
16821
|
+
else if (error) {
|
|
16822
|
+
bgColor = theme.message.color.warning; // Yellow
|
|
16823
|
+
textColor = palette.textPrimary;
|
|
16824
|
+
}
|
|
16825
|
+
// Priority 4: Default - no colors specified
|
|
16826
|
+
else {
|
|
16827
|
+
textColor = palette.primary;
|
|
16828
|
+
bgColor = addTransparency(palette.primary); // palette.primary + 12%
|
|
16829
|
+
}
|
|
16830
|
+
}
|
|
16831
|
+
else {
|
|
16832
|
+
// Inactive state
|
|
16833
|
+
if (color) {
|
|
16834
|
+
textColor = color; // Use color for text
|
|
16835
|
+
bgColor = addTransparency(color); // Uses default 1f (12%) transparency
|
|
16836
|
+
}
|
|
16837
|
+
// else: No color attribute - leave bgColor and textColor undefined
|
|
16838
|
+
// Styled component will use fallback for inactive: bg = rgba(0,0,0,0.06), text = palette.textSecondary
|
|
16839
|
+
}
|
|
16840
|
+
const renderIcon = React.useCallback((iconValue) => {
|
|
16841
|
+
if (!iconValue) {
|
|
16842
|
+
return null;
|
|
16843
|
+
}
|
|
16844
|
+
// If icon doesn't contain a dot and doesn't start with /sp/, it's a built-in icon from uilib-gl
|
|
16845
|
+
if (!iconValue.includes(".") && !iconValue.startsWith("/sp/")) {
|
|
16846
|
+
return jsxRuntime.jsx(uilibGl.Icon, { kind: iconValue });
|
|
16847
|
+
}
|
|
16848
|
+
// If it's an SVG file or resourceId path, use SvgImage component
|
|
16849
|
+
const isSvg = iconValue.endsWith(".svg") || iconValue.startsWith("/sp/resources/file/");
|
|
16850
|
+
if (isSvg) {
|
|
16851
|
+
const iconColor = textColor
|
|
16852
|
+
? textColor
|
|
16853
|
+
: isActive
|
|
16854
|
+
? palette.textContrast
|
|
16855
|
+
: palette.textSecondary;
|
|
16856
|
+
return (jsxRuntime.jsx(SvgImage, { url: iconValue, width: 14, height: 14, fontColor: iconColor }));
|
|
16857
|
+
}
|
|
16858
|
+
// Otherwise, it's a regular image
|
|
16859
|
+
return jsxRuntime.jsx("img", { src: iconValue, alt: "" });
|
|
16860
|
+
}, [isActive, textColor, palette.textContrast, palette.textSecondary]);
|
|
16861
|
+
return (jsxRuntime.jsxs(FilterChip, { "$isActive": isActive, "$bgColor": bgColor, "$textColor": textColor, ...props, children: [icon && jsxRuntime.jsx(ChipIconWrapper, { children: renderIcon(icon) }), jsxRuntime.jsx(ChipText, { "$maxTextWidth": maxTextWidth, children: text })] }));
|
|
16862
|
+
};
|
|
16863
|
+
|
|
16864
|
+
/**
|
|
16865
|
+
* Значение ячейки совпадает со значением пункта списка.
|
|
16866
|
+
*
|
|
16867
|
+
* Сравниваем строками: у варианта из конфига значение бывает строкой, а в ячейке числового
|
|
16868
|
+
* атрибута лежит число, и строгое сравнение не узнало бы собственный выбор.
|
|
16869
|
+
*/
|
|
16870
|
+
const isSameListValue = (left, right) => {
|
|
16871
|
+
if (lodash.isNil(left) || lodash.isNil(right)) {
|
|
16872
|
+
return lodash.isNil(left) && lodash.isNil(right);
|
|
16873
|
+
}
|
|
16874
|
+
return String(left) === String(right);
|
|
16875
|
+
};
|
|
16876
|
+
/** Пункт списка, выбранный в ячейке. Нет — значение в ячейке не из списка либо пусто. */
|
|
16877
|
+
const findListOption = (listOptions, value) => lodash.isNil(value) ? undefined : listOptions.find(({ value: optionValue }) => isSameListValue(optionValue, value));
|
|
16878
|
+
|
|
16879
|
+
/**
|
|
16880
|
+
* Колонка со списком значений в виде ряда кнопок (`control: "chips"`).
|
|
16881
|
+
*
|
|
16882
|
+
* Ряд виден всегда, как галка у логической колонки: выбор — один клик, без открытия редактора.
|
|
16883
|
+
* Выбранный пункт подсвечен, клик по нему снимает выбор — иначе очистить ячейку было бы нечем.
|
|
16884
|
+
* Без права правки ряд остаётся на месте и показывает выбор, но на клики не отвечает.
|
|
16885
|
+
*
|
|
16886
|
+
* Кнопки — те же, что у фильтра-чипсов: вариант со своим `color`/`icon` выглядит одинаково и там,
|
|
16887
|
+
* и здесь.
|
|
16888
|
+
*/
|
|
16889
|
+
const ChipsCell = React.memo(({ attribute, row, canEdit, onChange }) => {
|
|
16890
|
+
const { attributeName, isEditable, listOptions = [] } = attribute;
|
|
16891
|
+
const value = row.properties[attributeName];
|
|
16892
|
+
// Те же два условия, что и у обычной ячейки: режим таблицы и разрешение самого атрибута.
|
|
16893
|
+
const editable = canEdit && isEditable;
|
|
16894
|
+
const handleClick = React.useCallback((optionValue) => onChange(row.key, attributeName, isSameListValue(optionValue, value) ? null : optionValue), [attributeName, onChange, row.key, value]);
|
|
16895
|
+
if (!listOptions.length) {
|
|
16896
|
+
return (jsxRuntime.jsx(CellText, { children: jsxRuntime.jsx(CellPlaceholder, { children: "\u2014" }) }));
|
|
16897
|
+
}
|
|
16898
|
+
return (jsxRuntime.jsx(ChipsCellBox, { "$interactive": editable, children: listOptions.map(({ text, value: optionValue, color, icon }) => (jsxRuntime.jsx(CustomChip, { text: text, title: text, color: color, icon: icon ? getResourceUrl(icon) : undefined, isActive: isSameListValue(optionValue, value), onClick: editable ? () => handleClick(optionValue) : undefined }, String(optionValue)))) }));
|
|
16899
|
+
});
|
|
16900
|
+
|
|
16654
16901
|
/**
|
|
16655
16902
|
* Колонка цвета (`colorPicker` у атрибута).
|
|
16656
16903
|
*
|
|
@@ -16672,20 +16919,6 @@ const ColorCell = React.memo(({ attribute, row, canEdit, onChange }) => {
|
|
|
16672
16919
|
return (jsxRuntime.jsxs(ColorCellBox, { children: [editable ? (jsxRuntime.jsx(uilibGl.ColorPicker, { zIndex: DASHBOARD_OVERLAY_Z_INDEX, withOpacity: true, value: isValidColor ? color : new color$1.Color(DEFAULT_CELL_COLOR), onChange: handleChange })) : (isValidColor && jsxRuntime.jsx(ColorSwatch, { "$color": text })), jsxRuntime.jsx(CellText, { title: text, children: text || jsxRuntime.jsx(CellPlaceholder, { children: "\u2014" }) })] }));
|
|
16673
16920
|
});
|
|
16674
16921
|
|
|
16675
|
-
/**
|
|
16676
|
-
* Значение ячейки не изменилось.
|
|
16677
|
-
*
|
|
16678
|
-
* Даты сравниваем моментом времени, а не строкой: `DatePicker` отдаёт ISO с миллисекундами и
|
|
16679
|
-
* повторяет текущее значение на каждый уход фокуса из поля — без этой проверки строка
|
|
16680
|
-
* помечалась бы изменённой от одного лишь клика по иконке календаря.
|
|
16681
|
-
*/
|
|
16682
|
-
const isSameCellValue = (next, current, type) => {
|
|
16683
|
-
if (type === api.AttributeType.DateTime && next && current) {
|
|
16684
|
-
return new Date(next).getTime() === new Date(current).getTime();
|
|
16685
|
-
}
|
|
16686
|
-
return next === current;
|
|
16687
|
-
};
|
|
16688
|
-
|
|
16689
16922
|
/**
|
|
16690
16923
|
* Правка одной ячейки.
|
|
16691
16924
|
*
|
|
@@ -16755,9 +16988,85 @@ const useCellEditing = () => {
|
|
|
16755
16988
|
document.addEventListener("mousedown", onDocumentMouseDown);
|
|
16756
16989
|
return () => document.removeEventListener("mousedown", onDocumentMouseDown);
|
|
16757
16990
|
}, [editing]);
|
|
16991
|
+
// Закрыть правку из самого редактора — так же, как Enter: выбор из списка завершает правку сразу,
|
|
16992
|
+
// и держать открытым редактор с уже записанным значением незачем.
|
|
16993
|
+
const close = React.useCallback(() => setMode("focused"), []);
|
|
16758
16994
|
const buttonProps = React.useMemo(() => ({ ref: buttonRef, onFocus, onBlur, onClick }), [onBlur, onClick, onFocus]);
|
|
16759
16995
|
const editorProps = React.useMemo(() => ({ ref: editorRef, onBlur: onEditorBlur, onKeyDown: onEditorKeyDown }), [onEditorBlur, onEditorKeyDown]);
|
|
16760
|
-
return { editing, buttonProps, editorProps };
|
|
16996
|
+
return { editing, buttonProps, editorProps, close };
|
|
16997
|
+
};
|
|
16998
|
+
|
|
16999
|
+
/**
|
|
17000
|
+
* Раскрывает `Dropdown` сразу, как ячейка перешла в правку.
|
|
17001
|
+
*
|
|
17002
|
+
* Правку открывает фокус ячейки, а своего «открыться сразу» у `Dropdown` нет: без этого список
|
|
17003
|
+
* пришлось бы раскрывать вторым кликом, а с Tab-а — ещё и Enter-ом. Меню переключает клик по полю
|
|
17004
|
+
* списка; поле — родитель скрытого `input`, которым `Dropdown` ловит клавиатуру (`FocusTrap`), и
|
|
17005
|
+
* другого инпута внутри нет: поиск по пунктам живёт в портале.
|
|
17006
|
+
*/
|
|
17007
|
+
const useDropdownAutoOpen = (editing) => {
|
|
17008
|
+
const fieldRef = React.useRef(null);
|
|
17009
|
+
React.useEffect(() => {
|
|
17010
|
+
if (editing) {
|
|
17011
|
+
fieldRef.current?.querySelector("input")?.parentElement?.click();
|
|
17012
|
+
}
|
|
17013
|
+
}, [editing]);
|
|
17014
|
+
return fieldRef;
|
|
17015
|
+
};
|
|
17016
|
+
|
|
17017
|
+
/**
|
|
17018
|
+
* Колонка со списком значений в виде выпадающего списка (`control: "dropdown"`).
|
|
17019
|
+
*
|
|
17020
|
+
* Вне фокуса показывает подпись выбранного пункта, а сам список встаёт на фокус — как редакторы
|
|
17021
|
+
* остальных ячеек. Значение не из списка (справочник поменялся, строка пришла из источника)
|
|
17022
|
+
* показывается как есть, по `stringFormat` колонки: так видно, что в данных, а не пустота.
|
|
17023
|
+
*/
|
|
17024
|
+
const DropdownCell = React.memo(({ attribute, row, canEdit, onChange }) => {
|
|
17025
|
+
const { t } = useGlobalContext();
|
|
17026
|
+
const { editing, buttonProps, editorProps, close } = useCellEditing();
|
|
17027
|
+
const fieldRef = useDropdownAutoOpen(editing);
|
|
17028
|
+
const { attributeName, type, isEditable, stringFormat, multiline, listOptions = [] } = attribute;
|
|
17029
|
+
const value = row.properties[attributeName];
|
|
17030
|
+
const text = React.useMemo(() => {
|
|
17031
|
+
if (lodash.isNil(value)) {
|
|
17032
|
+
return "";
|
|
17033
|
+
}
|
|
17034
|
+
return findListOption(listOptions, value)?.text ?? formatAttributeValue({ t, type, value, stringFormat });
|
|
17035
|
+
}, [listOptions, stringFormat, t, type, value]);
|
|
17036
|
+
// Значения пунктов `Dropdown` сравнивает строго и только строками — числовые варианты иначе
|
|
17037
|
+
// не нашли бы себя в выбранном. Первым идёт пустой пункт: без него очистить ячейку нечем.
|
|
17038
|
+
const dropdownOptions = React.useMemo(() => [EMPTY_LIST_OPTION, ...listOptions.map(({ text: optionText, value: optionValue }) => ({
|
|
17039
|
+
text: optionText,
|
|
17040
|
+
value: String(optionValue),
|
|
17041
|
+
}))], [listOptions]);
|
|
17042
|
+
const handleChange = React.useCallback(([selected]) => {
|
|
17043
|
+
const next = findListOption(listOptions, selected?.value)?.value ?? null;
|
|
17044
|
+
if (!isSameListValue(next, value)) {
|
|
17045
|
+
onChange(row.key, attributeName, next);
|
|
17046
|
+
}
|
|
17047
|
+
close();
|
|
17048
|
+
}, [attributeName, close, listOptions, onChange, row.key, value]);
|
|
17049
|
+
if (!canEdit || !isEditable) {
|
|
17050
|
+
return (jsxRuntime.jsx(CellText, { "$multiline": multiline, title: text, children: text || jsxRuntime.jsx(CellPlaceholder, { children: "\u2014" }) }));
|
|
17051
|
+
}
|
|
17052
|
+
if (!editing) {
|
|
17053
|
+
return (jsxRuntime.jsx(CellButton, { type: "button", "$multiline": multiline, ...buttonProps, children: text || jsxRuntime.jsx(CellPlaceholder, { children: "\u2014" }) }));
|
|
17054
|
+
}
|
|
17055
|
+
return (jsxRuntime.jsxs(CellEditor, { ...editorProps, children: [jsxRuntime.jsx(CellGhost, { "$multiline": multiline, children: text || "—" }), jsxRuntime.jsx(CellField, { ref: fieldRef, children: jsxRuntime.jsx(uilibGl.Dropdown, { zIndex: DASHBOARD_OVERLAY_Z_INDEX, searchable: true, searchPlaceholder: t("structuredData.searchPlaceholder", { ns: "dashboard", defaultValue: "Найти" }), width: "100%", menuMinWidth: "12rem", options: dropdownOptions, value: lodash.isNil(value) ? "" : String(value), onChange: handleChange }) })] }));
|
|
17056
|
+
});
|
|
17057
|
+
|
|
17058
|
+
/**
|
|
17059
|
+
* Значение ячейки не изменилось.
|
|
17060
|
+
*
|
|
17061
|
+
* Даты сравниваем моментом времени, а не строкой: `DatePicker` отдаёт ISO с миллисекундами и
|
|
17062
|
+
* повторяет текущее значение на каждый уход фокуса из поля — без этой проверки строка
|
|
17063
|
+
* помечалась бы изменённой от одного лишь клика по иконке календаря.
|
|
17064
|
+
*/
|
|
17065
|
+
const isSameCellValue = (next, current, type) => {
|
|
17066
|
+
if (type === api.AttributeType.DateTime && next && current) {
|
|
17067
|
+
return new Date(next).getTime() === new Date(current).getTime();
|
|
17068
|
+
}
|
|
17069
|
+
return next === current;
|
|
16761
17070
|
};
|
|
16762
17071
|
|
|
16763
17072
|
/**
|
|
@@ -16770,7 +17079,7 @@ const useCellEditing = () => {
|
|
|
16770
17079
|
const TableCell = React.memo(({ attribute, row, canEdit, onChange }) => {
|
|
16771
17080
|
const { t, language } = useGlobalContext();
|
|
16772
17081
|
const { editing, buttonProps, editorProps } = useCellEditing();
|
|
16773
|
-
const { attributeName, type, subType, isEditable, stringFormat, multiline, colorPicker } = attribute;
|
|
17082
|
+
const { attributeName, type, subType, isEditable, stringFormat, multiline, colorPicker, control, listOptions } = attribute;
|
|
16774
17083
|
const value = row.properties[attributeName];
|
|
16775
17084
|
const handleChange = React.useCallback((next) => {
|
|
16776
17085
|
// Пустые правки отбрасываем: иначе строка становилась бы «изменённой» от простого
|
|
@@ -16799,6 +17108,11 @@ const TableCell = React.memo(({ attribute, row, canEdit, onChange }) => {
|
|
|
16799
17108
|
if (colorPicker) {
|
|
16800
17109
|
return jsxRuntime.jsx(ColorCell, { attribute: attribute, row: row, canEdit: canEdit, onChange: onChange });
|
|
16801
17110
|
}
|
|
17111
|
+
// Колонка со списком значений: вводить можно только то, что в списке, поэтому и редактор у неё
|
|
17112
|
+
// свой — ряд кнопок либо выпадающий список, каким бы ни был тип атрибута.
|
|
17113
|
+
if (listOptions) {
|
|
17114
|
+
return control === "chips" ? (jsxRuntime.jsx(ChipsCell, { attribute: attribute, row: row, canEdit: canEdit, onChange: onChange })) : (jsxRuntime.jsx(DropdownCell, { attribute: attribute, row: row, canEdit: canEdit, onChange: onChange }));
|
|
17115
|
+
}
|
|
16802
17116
|
// Логическое значение и на чтение показываем галкой, а не словами «true»/«false» —
|
|
16803
17117
|
// так же, как остальной дашборд рисует булевы атрибуты.
|
|
16804
17118
|
if (type === api.AttributeType.Boolean) {
|
|
@@ -16967,6 +17281,14 @@ const compareValues = (left, right, type) => {
|
|
|
16967
17281
|
// `numeric: true` держит строки с числами в человеческом порядке: «Дом 2» перед «Дом 10».
|
|
16968
17282
|
return String(left).localeCompare(String(right), undefined, { numeric: true });
|
|
16969
17283
|
};
|
|
17284
|
+
/**
|
|
17285
|
+
* Значение, по которому строка встаёт в порядок. У колонки со списком это подпись пункта, а не
|
|
17286
|
+
* записанное значение: пользователь видит подписи, и порядок по скрытым id выглядел бы случайным.
|
|
17287
|
+
*/
|
|
17288
|
+
const getSortValue = (row, attributeName, listOptions) => {
|
|
17289
|
+
const value = row.properties[attributeName];
|
|
17290
|
+
return listOptions ? findListOption(listOptions, value)?.text ?? value : value;
|
|
17291
|
+
};
|
|
16970
17292
|
/**
|
|
16971
17293
|
* Сортировка представления. Порядок строк в черновике не меняется — он же уходит в фильтр,
|
|
16972
17294
|
* а сортировка остаётся видом.
|
|
@@ -16978,15 +17300,17 @@ const sortRows = (rows, sort, schema) => {
|
|
|
16978
17300
|
if (!sort) {
|
|
16979
17301
|
return rows;
|
|
16980
17302
|
}
|
|
16981
|
-
const { type } = schema.find(({ attributeName }) => attributeName === sort.attributeName) || {};
|
|
17303
|
+
const { type, listOptions } = schema.find(({ attributeName }) => attributeName === sort.attributeName) || {};
|
|
16982
17304
|
const direction = sort.direction === "asc" ? 1 : -1;
|
|
17305
|
+
// Подписи списка сравниваются как строки, какого бы типа ни было само значение.
|
|
17306
|
+
const compareType = listOptions ? api.AttributeType.String : type;
|
|
16983
17307
|
return [...rows].sort((leftRow, rightRow) => {
|
|
16984
|
-
const left = leftRow
|
|
16985
|
-
const right = rightRow
|
|
17308
|
+
const left = getSortValue(leftRow, sort.attributeName, listOptions);
|
|
17309
|
+
const right = getSortValue(rightRow, sort.attributeName, listOptions);
|
|
16986
17310
|
if (isEmptyValue(left) || isEmptyValue(right)) {
|
|
16987
17311
|
return isEmptyValue(left) && isEmptyValue(right) ? 0 : isEmptyValue(left) ? 1 : -1;
|
|
16988
17312
|
}
|
|
16989
|
-
return compareValues(left, right,
|
|
17313
|
+
return compareValues(left, right, compareType) * direction;
|
|
16990
17314
|
});
|
|
16991
17315
|
};
|
|
16992
17316
|
/** Клик по заголовку: по возрастанию → по убыванию → исходный порядок. */
|
|
@@ -17635,11 +17959,22 @@ const ModalTitleText = styled(uilibGl.Flex).withConfig({ displayName: "ModalTitl
|
|
|
17635
17959
|
const ModalCloseButton = styled(uilibGl.IconButton).withConfig({ displayName: "ModalCloseButton", componentId: "sc-7ns06w" }) `
|
|
17636
17960
|
margin: -0.625rem -1rem 0 1rem;
|
|
17637
17961
|
`;
|
|
17962
|
+
/**
|
|
17963
|
+
* Каждого прямого ребёнка модалки `ContainerChildren` (`isMain`) оборачивает карточкой дашборда
|
|
17964
|
+
* `ContainerWrapper`. `noBorders` снимает с неё тень и отступ, но не фон (90% белого) и не
|
|
17965
|
+
* `backdrop-filter` — поверх шапки они легли бы белой размытой подложкой под каждым ребёнком.
|
|
17966
|
+
* Селектор прямой: вложенные карточки страниц внутри содержимого остаются как есть.
|
|
17967
|
+
*/
|
|
17638
17968
|
const ModalContent = styled(uilibGl.DialogContent).withConfig({ displayName: "ModalContent", componentId: "sc-1p8tdpb" }) `
|
|
17639
17969
|
&& {
|
|
17640
17970
|
padding: 0 calc(${MODAL_PADDING} - ${MODAL_SCROLLBAR_WIDTH}) 0 ${MODAL_PADDING};
|
|
17641
17971
|
scrollbar-gutter: stable;
|
|
17642
17972
|
}
|
|
17973
|
+
|
|
17974
|
+
> ${Container} > ${ContainerWrapper} {
|
|
17975
|
+
background: transparent;
|
|
17976
|
+
backdrop-filter: none;
|
|
17977
|
+
}
|
|
17643
17978
|
`;
|
|
17644
17979
|
|
|
17645
17980
|
const isHiddenEmptyValue = ({ value, children, hideEmpty, renderElement, }) => {
|
|
@@ -18503,138 +18838,6 @@ const ChipsContainer = styled(uilibGl.Flex).withConfig({ displayName: "ChipsCont
|
|
|
18503
18838
|
justify-content: ${({ $align }) => getJustifyContent($align)};
|
|
18504
18839
|
`;
|
|
18505
18840
|
|
|
18506
|
-
const FilterChip = styled.div.withConfig({ displayName: "FilterChip", componentId: "sc-17fod1d" }) `
|
|
18507
|
-
display: inline-flex;
|
|
18508
|
-
align-items: center;
|
|
18509
|
-
gap: 0.25rem;
|
|
18510
|
-
padding: 0.3125rem 0.5rem;
|
|
18511
|
-
height: 1.5rem;
|
|
18512
|
-
border-radius: 0.25rem;
|
|
18513
|
-
background-color: ${({ $isActive, $bgColor, theme }) => $bgColor
|
|
18514
|
-
? $bgColor
|
|
18515
|
-
: $isActive
|
|
18516
|
-
? theme.palette?.primary
|
|
18517
|
-
: theme.palette?.elementLight};
|
|
18518
|
-
color: ${({ $isActive, $textColor, theme }) => $textColor
|
|
18519
|
-
? $textColor
|
|
18520
|
-
: $isActive
|
|
18521
|
-
? theme.palette?.textContrast
|
|
18522
|
-
: theme.palette?.textSecondary};
|
|
18523
|
-
cursor: pointer;
|
|
18524
|
-
font-size: 0.75rem;
|
|
18525
|
-
line-height: 0.875rem;
|
|
18526
|
-
white-space: nowrap;
|
|
18527
|
-
flex-shrink: 0;
|
|
18528
|
-
transition: all 0.2s ease-in-out;
|
|
18529
|
-
margin: 0 0.25rem 0 0;
|
|
18530
|
-
box-sizing: border-box;
|
|
18531
|
-
|
|
18532
|
-
&:hover {
|
|
18533
|
-
background-color: ${({ $isActive, $bgColor, theme }) => $isActive
|
|
18534
|
-
? $bgColor || 'inherit'
|
|
18535
|
-
: $bgColor || theme.palette?.elementDark};
|
|
18536
|
-
}
|
|
18537
|
-
`;
|
|
18538
|
-
const ChipIconWrapper = styled.span.withConfig({ displayName: "ChipIconWrapper", componentId: "sc-8j3rkq" }) `
|
|
18539
|
-
display: inline-flex;
|
|
18540
|
-
align-items: center;
|
|
18541
|
-
justify-content: center;
|
|
18542
|
-
width: 0.875rem;
|
|
18543
|
-
height: 0.875rem;
|
|
18544
|
-
flex-shrink: 0;
|
|
18545
|
-
|
|
18546
|
-
svg, img {
|
|
18547
|
-
width: 100%;
|
|
18548
|
-
height: 100%;
|
|
18549
|
-
display: block;
|
|
18550
|
-
}
|
|
18551
|
-
`;
|
|
18552
|
-
const ChipText = styled.span.withConfig({ displayName: "ChipText", componentId: "sc-1saygd0" }) `
|
|
18553
|
-
overflow: hidden;
|
|
18554
|
-
text-overflow: ellipsis;
|
|
18555
|
-
white-space: nowrap;
|
|
18556
|
-
${({ $maxTextWidth }) => $maxTextWidth && `max-width: ${$maxTextWidth / 16}rem;`}
|
|
18557
|
-
`;
|
|
18558
|
-
|
|
18559
|
-
const CustomChip = ({ text, icon, color, primary, secondary, error, disabled, isActive, maxTextWidth, fontColor: customFontColor, backgroundColor: customBackgroundColor, ...props }) => {
|
|
18560
|
-
const theme = styled.useTheme();
|
|
18561
|
-
const { palette } = theme;
|
|
18562
|
-
// Helper to add transparency to hex color
|
|
18563
|
-
const addTransparency = (hex, transparency = '1f') => {
|
|
18564
|
-
// Ensure hex starts with #
|
|
18565
|
-
const cleanHex = hex.startsWith('#') ? hex : `#${hex}`;
|
|
18566
|
-
return `${cleanHex}${transparency}`;
|
|
18567
|
-
};
|
|
18568
|
-
// Calculate colors based on priority:
|
|
18569
|
-
// 1. customFontColor / customBackgroundColor (highest, from options)
|
|
18570
|
-
// 2. color (from attribute)
|
|
18571
|
-
// 3. primary/secondary/error
|
|
18572
|
-
// 4. default theme colors
|
|
18573
|
-
let bgColor;
|
|
18574
|
-
let textColor;
|
|
18575
|
-
if (isActive) {
|
|
18576
|
-
// Priority 1: Custom colors from options
|
|
18577
|
-
if (customFontColor) {
|
|
18578
|
-
textColor = customFontColor;
|
|
18579
|
-
bgColor = customBackgroundColor || addTransparency(customFontColor); // 12% opacity
|
|
18580
|
-
}
|
|
18581
|
-
// Priority 2: Color from attribute
|
|
18582
|
-
else if (color) {
|
|
18583
|
-
textColor = palette.textPrimary; // White text for active chip
|
|
18584
|
-
bgColor = color; // Full color without transparency
|
|
18585
|
-
}
|
|
18586
|
-
// Priority 3: Variant colors
|
|
18587
|
-
else if (primary) {
|
|
18588
|
-
bgColor = theme.message.color.success; // Green
|
|
18589
|
-
textColor = palette.textContrast;
|
|
18590
|
-
}
|
|
18591
|
-
else if (secondary) {
|
|
18592
|
-
bgColor = palette.primary;
|
|
18593
|
-
textColor = palette.textContrast;
|
|
18594
|
-
}
|
|
18595
|
-
else if (error) {
|
|
18596
|
-
bgColor = theme.message.color.warning; // Yellow
|
|
18597
|
-
textColor = palette.textPrimary;
|
|
18598
|
-
}
|
|
18599
|
-
// Priority 4: Default - no colors specified
|
|
18600
|
-
else {
|
|
18601
|
-
textColor = palette.primary;
|
|
18602
|
-
bgColor = addTransparency(palette.primary); // palette.primary + 12%
|
|
18603
|
-
}
|
|
18604
|
-
}
|
|
18605
|
-
else {
|
|
18606
|
-
// Inactive state
|
|
18607
|
-
if (color) {
|
|
18608
|
-
textColor = color; // Use color for text
|
|
18609
|
-
bgColor = addTransparency(color); // Uses default 1f (12%) transparency
|
|
18610
|
-
}
|
|
18611
|
-
// else: No color attribute - leave bgColor and textColor undefined
|
|
18612
|
-
// Styled component will use fallback for inactive: bg = rgba(0,0,0,0.06), text = palette.textSecondary
|
|
18613
|
-
}
|
|
18614
|
-
const renderIcon = React.useCallback((iconValue) => {
|
|
18615
|
-
if (!iconValue) {
|
|
18616
|
-
return null;
|
|
18617
|
-
}
|
|
18618
|
-
// If icon doesn't contain a dot and doesn't start with /sp/, it's a built-in icon from uilib-gl
|
|
18619
|
-
if (!iconValue.includes(".") && !iconValue.startsWith("/sp/")) {
|
|
18620
|
-
return jsxRuntime.jsx(uilibGl.Icon, { kind: iconValue });
|
|
18621
|
-
}
|
|
18622
|
-
// If it's an SVG file or resourceId path, use SvgImage component
|
|
18623
|
-
const isSvg = iconValue.endsWith(".svg") || iconValue.startsWith("/sp/resources/file/");
|
|
18624
|
-
if (isSvg) {
|
|
18625
|
-
const iconColor = textColor
|
|
18626
|
-
? textColor
|
|
18627
|
-
: isActive
|
|
18628
|
-
? palette.textContrast
|
|
18629
|
-
: palette.textSecondary;
|
|
18630
|
-
return (jsxRuntime.jsx(SvgImage, { url: iconValue, width: 14, height: 14, fontColor: iconColor }));
|
|
18631
|
-
}
|
|
18632
|
-
// Otherwise, it's a regular image
|
|
18633
|
-
return jsxRuntime.jsx("img", { src: iconValue, alt: "" });
|
|
18634
|
-
}, [isActive, textColor, palette.textContrast, palette.textSecondary]);
|
|
18635
|
-
return (jsxRuntime.jsxs(FilterChip, { "$isActive": isActive, "$bgColor": bgColor, "$textColor": textColor, ...props, children: [icon && jsxRuntime.jsx(ChipIconWrapper, { children: renderIcon(icon) }), jsxRuntime.jsx(ChipText, { "$maxTextWidth": maxTextWidth, children: text })] }));
|
|
18636
|
-
};
|
|
18637
|
-
|
|
18638
18841
|
const ChipsFilter = ({ type, filter, elementConfig, }) => {
|
|
18639
18842
|
const { filters, dataSources, changeFilters } = useWidgetContext(type);
|
|
18640
18843
|
const { currentPage } = useWidgetPage(type);
|