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