@evergis/react 4.0.137 → 4.0.138

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.
Files changed (23) hide show
  1. package/dist/components/Dashboard/componentTypes.d.ts +10 -1
  2. package/dist/components/Dashboard/containers/VoteContainer/components/VoteCreateForm.d.ts +3 -0
  3. package/dist/components/Dashboard/containers/VoteContainer/components/VoteResults.d.ts +3 -0
  4. package/dist/components/Dashboard/containers/VoteContainer/components/VoteScreen.d.ts +9 -0
  5. package/dist/components/Dashboard/containers/VoteContainer/components/VoteShare.d.ts +3 -0
  6. package/dist/components/Dashboard/containers/VoteContainer/constants.d.ts +46 -0
  7. package/dist/components/Dashboard/containers/VoteContainer/hooks/useVoteActions.d.ts +26 -0
  8. package/dist/components/Dashboard/containers/VoteContainer/hooks/useVoteContainer.d.ts +25 -0
  9. package/dist/components/Dashboard/containers/VoteContainer/hooks/useVoteData.d.ts +22 -0
  10. package/dist/components/Dashboard/containers/VoteContainer/hooks/useVoteForm.d.ts +17 -0
  11. package/dist/components/Dashboard/containers/VoteContainer/hooks/useVoteUser.d.ts +14 -0
  12. package/dist/components/Dashboard/containers/VoteContainer/index.d.ts +11 -0
  13. package/dist/components/Dashboard/containers/VoteContainer/styled.d.ts +31 -0
  14. package/dist/components/Dashboard/containers/VoteContainer/types.d.ts +88 -0
  15. package/dist/components/Dashboard/containers/VoteContainer/utils.d.ts +38 -0
  16. package/dist/components/Dashboard/containers/index.d.ts +1 -0
  17. package/dist/components/Dashboard/containers/registry.d.ts +1 -0
  18. package/dist/components/Dashboard/types.d.ts +16 -2
  19. package/dist/index.js +961 -229
  20. package/dist/index.js.map +1 -1
  21. package/dist/react.esm.js +963 -232
  22. package/dist/react.esm.js.map +1 -1
  23. package/package.json +2 -2
package/dist/react.esm.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import styled, { createGlobalStyle, css, useTheme } from 'styled-components';
2
- import { Icon, IconButtonInnerChild, IconButton, Flex, transition, Chip, shadows, Description, Divider, Popup, Menu, IconToggleButton, LinearProgress, Tooltip as Tooltip$1, IconToggle, Preview, LegendToggler, DropdownField, MultiSelectContainer, IconButtonButton, useDragAndDropEffect, FlatButton, DraggableTreeContainer, DraggableTree, FlexSpan, Dialog, DialogTitle, ActionsGroup, DialogContent, DialogActions, RaisedButton, UploaderItemArea, UploaderTitleWrapper, ThemeProvider, darkTheme, CircularProgress, Switch, AutoComplete, Input, Slider, Dropdown, Checkbox, DatePicker, getLocale, H2, Blank, Popover, NumberInput, Uploader, NumberRangeSlider, useAsyncAutocomplete, RangeNumberInput, TreeDropdown, defaultTheme, dateFormat } from '@evergis/uilib-gl';
2
+ import { Icon, IconButtonInnerChild, IconButton, Flex, transition, Chip, shadows, Description, Divider, Popup, Menu, IconToggleButton, LinearProgress, Tooltip as Tooltip$1, IconToggle, Preview, LegendToggler, DropdownField, MultiSelectContainer, IconButtonButton, useDragAndDropEffect, FlatButton, DraggableTreeContainer, DraggableTree, FlexSpan, Dialog, DialogTitle, ActionsGroup, DialogContent, DialogActions, RaisedButton, UploaderItemArea, UploaderTitleWrapper, RadioGroup, Dropdown, Input, Switch, Checkbox, Radio, ThemeProvider, darkTheme, CircularProgress, AutoComplete, Slider, DatePicker, getLocale, H2, Blank, Popover, NumberInput, Uploader, NumberRangeSlider, useAsyncAutocomplete, RangeNumberInput, TreeDropdown, defaultTheme, dateFormat } from '@evergis/uilib-gl';
3
3
  import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
4
4
  import { isValidElement, Fragment, createContext, memo, useRef, useState, useCallback, useEffect, useContext, useMemo, createElement, useLayoutEffect, forwardRef } from 'react';
5
5
  import { barChartClassNames, lineChartClassNames, BarChart as BarChart$1, LineChart, PieChart } from '@evergis/charts';
6
6
  import { AttributeType, AttributeIconType, generateId, STORAGE_TOKEN_KEY, parseJwt, STORAGE_REFRESH_TOKEN_KEY, RemoteTaskStatus, AttributeConfigurationType, LayerServiceType, OgcGeometryType, StringSubType } from '@evergis/api';
7
7
  import { isNil, isEqual, isEmpty, uniqueId, unescape } from 'lodash';
8
- import { Color as Color$1, ColorScale } from '@evergis/color';
8
+ import { ColorScale, Color as Color$1 } from '@evergis/color';
9
9
  import { isValid, format, parseJSON, parseISO, toDate } from 'date-fns';
10
10
  import { ru, enUS } from 'date-fns/locale';
11
11
  import { stringify } from 'wkt';
@@ -3295,6 +3295,7 @@ var ContainerTemplate;
3295
3295
  ContainerTemplate["Attachment"] = "Attachment";
3296
3296
  ContainerTemplate["Divider"] = "Divider";
3297
3297
  ContainerTemplate["StructuredData"] = "StructuredData";
3298
+ ContainerTemplate["Vote"] = "Vote";
3298
3299
  })(ContainerTemplate || (ContainerTemplate = {}));
3299
3300
  var HeaderTemplate;
3300
3301
  (function (HeaderTemplate) {
@@ -5377,169 +5378,6 @@ const getChartMarkers = (items, markers, dataSources) => {
5377
5378
  })) || []);
5378
5379
  };
5379
5380
 
5380
- const ChartLegendContainer = styled(Flex).withConfig({ displayName: "ChartLegendContainer", componentId: "sc-1di1zl9" }) `
5381
- flex-direction: column;
5382
- flex-wrap: wrap;
5383
- justify-content: ${({ twoColumns }) => (twoColumns ? "flex-start" : "center")};
5384
- `;
5385
- const ChartLegendItem = styled(Flex).withConfig({ displayName: "ChartLegendItem", componentId: "sc-16m9piw" }) `
5386
- cursor: ${({ onClick }) => (onClick ? "pointer" : "default")};
5387
- align-items: center;
5388
- flex-wrap: nowrap;
5389
- width: auto;
5390
- margin-right: 0.375rem;
5391
- margin-bottom: 0.25rem;
5392
- line-height: 0.75rem;
5393
- opacity: ${({ isFiltered, hasAnyFilter }) => (isFiltered ? 1 : hasAnyFilter ? FILTERED_VALUE_OPACITY / 100 : 1)};
5394
- `;
5395
- const ChartLegendColor = styled.div.withConfig({ displayName: "ChartLegendColor", componentId: "sc-zxnnio" }) `
5396
- width: 0.5rem;
5397
- height: 0.5rem;
5398
- margin-right: 0.375rem;
5399
- background-color: ${({ color }) => color};
5400
- border-radius: ${({ theme: { borderRadius } }) => borderRadius.tiny};
5401
- `;
5402
- const ChartLegendName = styled.div.withConfig({ displayName: "ChartLegendName", componentId: "sc-isz576" }) `
5403
- flex: 1;
5404
- font-size: ${({ $fontSize }) => $fontSize || "0.625rem"};
5405
- color: ${({ theme: { palette }, $fontColor }) => $fontColor || palette.textPrimary};
5406
- `;
5407
-
5408
- const ContainerAlias = styled(Flex).withConfig({ displayName: "ContainerAlias", componentId: "sc-1ftaxyu" }) `
5409
- align-items: center;
5410
- flex-wrap: nowrap;
5411
- font-size: 0.75rem;
5412
- color: ${({ theme: { palette } }) => palette.textSecondary};
5413
-
5414
- &&& {
5415
- margin-bottom: ${({ hasBottomMargin }) => (hasBottomMargin ? 0.25 : 0)}rem;
5416
- }
5417
-
5418
- span[kind] {
5419
- margin-right: 0.5rem;
5420
-
5421
- :after {
5422
- color: ${({ theme: { palette } }) => palette.primary};
5423
- }
5424
- }
5425
- `;
5426
- const ContainerAliasIcon = styled.div.withConfig({ displayName: "ContainerAliasIcon", componentId: "sc-elezmi" }) `
5427
- margin-right: 0.5rem;
5428
- `;
5429
- const ContainerChart = styled(Flex).withConfig({ displayName: "ContainerChart", componentId: "sc-16sh3wn" }) `
5430
- justify-content: flex-start;
5431
-
5432
- > * {
5433
- display: flex;
5434
- justify-content: center;
5435
- width: 100%;
5436
- }
5437
-
5438
- /* fill: слот забирает остаток высоты ячейки (под слотами title/alias/legend) и центрирует тело графика. */
5439
- ${({ $fill }) => $fill &&
5440
- css `
5441
- flex: 1 1 auto;
5442
- min-height: 0;
5443
- align-items: center;
5444
- justify-content: center;
5445
-
5446
- > * {
5447
- height: 100%;
5448
- }
5449
- `}
5450
- `;
5451
- const ContainerLegend = styled(Flex).withConfig({ displayName: "ContainerLegend", componentId: "sc-z1n1s8" }) ``;
5452
- const ContainerUnits = styled.div.withConfig({ displayName: "ContainerUnits", componentId: "sc-1vurgy9" }) `
5453
- margin-left: 0.5rem;
5454
- white-space: nowrap;
5455
- font-size: 0.75rem;
5456
- `;
5457
- const ContainerValue = styled(Flex).withConfig({ displayName: "ContainerValue", componentId: "sc-aj1rs9" }) `
5458
- justify-content: flex-end;
5459
- align-items: center;
5460
- flex-wrap: nowrap;
5461
- width: 100%;
5462
- font-size: ${({ big }) => (big ? 1.5 : 1)}rem;
5463
- color: ${({ fontColor, theme: { palette } }) => fontColor || palette.textPrimary};
5464
-
5465
- /* fill: строка со слотом chart+legend забирает остаток высоты Container'а (гейт — только у Chart-контейнера). */
5466
- ${({ $fill }) => $fill &&
5467
- css `
5468
- flex: 1 1 auto;
5469
- min-height: 0;
5470
- `}
5471
-
5472
- > * {
5473
- width: ${({ column }) => (column ? "100%" : "auto")};
5474
- }
5475
-
5476
- ${ContainerChart}, ${ContainerLegend} {
5477
- width: ${({ column }) => (column ? "100%" : "50%")};
5478
- }
5479
-
5480
- ${ContainerLegend} {
5481
- margin-left: ${({ column }) => (column ? 0 : "1rem")};
5482
- }
5483
-
5484
- ${ChartLegendContainer} {
5485
- flex-direction: ${({ column }) => (column ? "row" : "column")};
5486
- margin-top: ${({ column }) => (column ? "1rem" : 0)};
5487
- }
5488
- `;
5489
- const ColorIconMixin = css `
5490
- :after {
5491
- color: ${({ $fontColor }) => $fontColor} !important;
5492
- }
5493
- `;
5494
- const SizeIconMixin = css `
5495
- :after {
5496
- font-size: ${({ $fontSize }) => $fontSize}px !important;
5497
- }
5498
- `;
5499
- styled(Icon).withConfig({ displayName: "ContainerIcon", componentId: "sc-1w9w0bq" }) `
5500
- width: auto;
5501
- height: auto;
5502
- margin-bottom: 0.5rem;
5503
- ${({ $fontColor }) => !!$fontColor && ColorIconMixin};
5504
- ${({ $fontSize }) => !!$fontSize && SizeIconMixin};
5505
- `;
5506
- const SvgContainerColorMixin$1 = css `
5507
- path,
5508
- line,
5509
- circle {
5510
- fill: ${({ $fontColor }) => $fontColor};
5511
- }
5512
- `;
5513
- const SvgContainer$1 = styled.div.withConfig({ displayName: "SvgContainer", componentId: "sc-1qs5gmx" }) `
5514
- &&& {
5515
- min-width: ${({ $width }) => ($width ? `${$width}px` : "1rem")};
5516
- max-width: ${({ $width }) => ($width ? `${$width}px` : "1rem")};
5517
-
5518
- ${({ $fontColor }) => !!$fontColor && SvgContainerColorMixin$1};
5519
-
5520
- > * {
5521
- min-width: inherit;
5522
- }
5523
- }
5524
- `;
5525
- const TwoColumnContainerWrapper = styled(Container).withConfig({ displayName: "TwoColumnContainerWrapper", componentId: "sc-1ogv4d3" }) `
5526
- flex-direction: row;
5527
- flex-wrap: nowrap;
5528
- align-items: center;
5529
-
5530
- > * {
5531
- flex: 1;
5532
- }
5533
-
5534
- > ${ContainerValue} {
5535
- justify-content: flex-end;
5536
-
5537
- > * {
5538
- text-align: right;
5539
- }
5540
- }
5541
- `;
5542
-
5543
5381
  const DashboardContext = createContext({});
5544
5382
  const DashboardProvider = memo(({ children, ...props }) => {
5545
5383
  return jsx(DashboardContext.Provider, { value: props, children: children });
@@ -8665,61 +8503,6 @@ const useUpdateDataSource = ({ dataSource, config, filters, attributes, layerPar
8665
8503
  }, [dataSource, getDataSourcePromises, getUpdatedDataSources, dataSources]);
8666
8504
  };
8667
8505
 
8668
- const useRenderContainer = ({ elementConfig, type, renderElement, renderBody, }) => {
8669
- const { attributes } = useWidgetContext(type);
8670
- const { getRenderContainerItem, attributesToRender } = useContainerAttributes({
8671
- elementConfig,
8672
- type,
8673
- renderElement,
8674
- });
8675
- const renderContainer = useCallback((attribute) => {
8676
- const item = getRenderContainerItem(elementConfig, attribute);
8677
- const itemAttribute = attribute
8678
- ? attributes?.find(({ attributeName }) => attributeName === attribute)
8679
- : undefined;
8680
- const overrideTemplateName = getDisplayTemplateNameFromAttribute(itemAttribute);
8681
- if (overrideTemplateName) {
8682
- const OverrideContainer = getContainerComponent(overrideTemplateName);
8683
- if (OverrideContainer) {
8684
- const itemConfig = {
8685
- ...elementConfig,
8686
- children: (elementConfig?.children ?? []).map(child => ({
8687
- ...child,
8688
- attributeName: attribute,
8689
- })),
8690
- templateName: overrideTemplateName,
8691
- };
8692
- return (jsx(OverrideContainer, { type: type, elementConfig: itemConfig, renderElement: item.render }, attribute));
8693
- }
8694
- }
8695
- if (isEmptyElementValue(item.value) && item.hideEmpty)
8696
- return null;
8697
- return renderBody(item, attribute);
8698
- }, [getRenderContainerItem, elementConfig, attributes, type, renderBody]);
8699
- return { renderContainer, attributesToRender };
8700
- };
8701
-
8702
- const OneColumnContainer = memo(({ type, elementConfig, renderElement }) => {
8703
- const { width, height, overflow } = elementConfig?.options || {};
8704
- const templateName = elementConfig?.templateName;
8705
- const bgHost = useBgImageHost(elementConfig);
8706
- const renderBody = useCallback(({ id, value, style, hasUnits, render }) => (jsxs(Container, { id: id, isColumn: true, "data-templatename": templateName, style: style, "$sizeCss": getWrapperSizeStyle({
8707
- style,
8708
- width,
8709
- height,
8710
- overflow,
8711
- defaults: BASE_CONTAINER_STYLE,
8712
- defaultWidth: FILL_SIZE,
8713
- }), ...bgHost, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: render }), jsxs(ContainerAlias, { hasBottomMargin: true, children: [render({ id: "alias" }), render({ id: "tooltip" }), render({ id: "modal" })] }), jsxs(ContainerValue, { children: [value, hasUnits && (jsx(ContainerUnits, { children: render({ id: "units" }) }))] })] })), [width, height, overflow, templateName, bgHost, elementConfig]);
8714
- const { renderContainer, attributesToRender } = useRenderContainer({
8715
- type,
8716
- elementConfig,
8717
- renderElement,
8718
- renderBody,
8719
- });
8720
- return attributesToRender ? (jsx(Fragment$1, { children: attributesToRender.map(renderContainer) })) : (renderContainer());
8721
- });
8722
-
8723
8506
  /** Обычная (не сеточная) раскладка группы: дети в колонку или в строку. */
8724
8507
  const FlowGroup = memo(({ elementConfig, type, renderElement }) => {
8725
8508
  const { expandedContainers } = useWidgetContext(type);
@@ -9272,18 +9055,181 @@ const AttachmentContainer = memo(({ type, elementConfig, renderElement }) => {
9272
9055
  }) }, previewIndex))] }) }))] }));
9273
9056
  });
9274
9057
 
9275
- const CameraContainer = memo(({ elementConfig, type, renderElement }) => {
9276
- const { expandedContainers } = useWidgetContext(type);
9277
- const { id, options } = elementConfig || {};
9278
- const { expandable, expanded } = options || {};
9279
- const isVisible = isVisibleContainer(id, expandable, expanded, expandedContainers);
9280
- const { root, body } = useContainerRoot({ elementConfig });
9281
- return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsxs(Container, { ...body, children: [jsx(ContainerAlias, { hasBottomMargin: true, children: renderElement({ id: "alias" }) }), jsx(ContainerValue, { children: renderElement({ id: "value", wrap: false }) })] }))] }));
9282
- });
9058
+ const ChartLegendContainer = styled(Flex).withConfig({ displayName: "ChartLegendContainer", componentId: "sc-1di1zl9" }) `
9059
+ flex-direction: column;
9060
+ flex-wrap: wrap;
9061
+ justify-content: ${({ twoColumns }) => (twoColumns ? "flex-start" : "center")};
9062
+ `;
9063
+ const ChartLegendItem = styled(Flex).withConfig({ displayName: "ChartLegendItem", componentId: "sc-16m9piw" }) `
9064
+ cursor: ${({ onClick }) => (onClick ? "pointer" : "default")};
9065
+ align-items: center;
9066
+ flex-wrap: nowrap;
9067
+ width: auto;
9068
+ margin-right: 0.375rem;
9069
+ margin-bottom: 0.25rem;
9070
+ line-height: 0.75rem;
9071
+ opacity: ${({ isFiltered, hasAnyFilter }) => (isFiltered ? 1 : hasAnyFilter ? FILTERED_VALUE_OPACITY / 100 : 1)};
9072
+ `;
9073
+ const ChartLegendColor = styled.div.withConfig({ displayName: "ChartLegendColor", componentId: "sc-zxnnio" }) `
9074
+ width: 0.5rem;
9075
+ height: 0.5rem;
9076
+ margin-right: 0.375rem;
9077
+ background-color: ${({ color }) => color};
9078
+ border-radius: ${({ theme: { borderRadius } }) => borderRadius.tiny};
9079
+ `;
9080
+ const ChartLegendName = styled.div.withConfig({ displayName: "ChartLegendName", componentId: "sc-isz576" }) `
9081
+ flex: 1;
9082
+ font-size: ${({ $fontSize }) => $fontSize || "0.625rem"};
9083
+ color: ${({ theme: { palette }, $fontColor }) => $fontColor || palette.textPrimary};
9084
+ `;
9283
9085
 
9284
- /**
9285
- * Контекст вписывания графика (`options.fill` контейнера Chart).
9286
- *
9086
+ const ContainerAlias = styled(Flex).withConfig({ displayName: "ContainerAlias", componentId: "sc-1ftaxyu" }) `
9087
+ align-items: center;
9088
+ flex-wrap: nowrap;
9089
+ font-size: 0.75rem;
9090
+ color: ${({ theme: { palette } }) => palette.textSecondary};
9091
+
9092
+ &&& {
9093
+ margin-bottom: ${({ hasBottomMargin }) => (hasBottomMargin ? 0.25 : 0)}rem;
9094
+ }
9095
+
9096
+ span[kind] {
9097
+ margin-right: 0.5rem;
9098
+
9099
+ :after {
9100
+ color: ${({ theme: { palette } }) => palette.primary};
9101
+ }
9102
+ }
9103
+ `;
9104
+ const ContainerAliasIcon = styled.div.withConfig({ displayName: "ContainerAliasIcon", componentId: "sc-elezmi" }) `
9105
+ margin-right: 0.5rem;
9106
+ `;
9107
+ const ContainerChart = styled(Flex).withConfig({ displayName: "ContainerChart", componentId: "sc-16sh3wn" }) `
9108
+ justify-content: flex-start;
9109
+
9110
+ > * {
9111
+ display: flex;
9112
+ justify-content: center;
9113
+ width: 100%;
9114
+ }
9115
+
9116
+ /* fill: слот забирает остаток высоты ячейки (под слотами title/alias/legend) и центрирует тело графика. */
9117
+ ${({ $fill }) => $fill &&
9118
+ css `
9119
+ flex: 1 1 auto;
9120
+ min-height: 0;
9121
+ align-items: center;
9122
+ justify-content: center;
9123
+
9124
+ > * {
9125
+ height: 100%;
9126
+ }
9127
+ `}
9128
+ `;
9129
+ const ContainerLegend = styled(Flex).withConfig({ displayName: "ContainerLegend", componentId: "sc-z1n1s8" }) ``;
9130
+ const ContainerUnits = styled.div.withConfig({ displayName: "ContainerUnits", componentId: "sc-1vurgy9" }) `
9131
+ margin-left: 0.5rem;
9132
+ white-space: nowrap;
9133
+ font-size: 0.75rem;
9134
+ `;
9135
+ const ContainerValue = styled(Flex).withConfig({ displayName: "ContainerValue", componentId: "sc-aj1rs9" }) `
9136
+ justify-content: flex-end;
9137
+ align-items: center;
9138
+ flex-wrap: nowrap;
9139
+ width: 100%;
9140
+ font-size: ${({ big }) => (big ? 1.5 : 1)}rem;
9141
+ color: ${({ fontColor, theme: { palette } }) => fontColor || palette.textPrimary};
9142
+
9143
+ /* fill: строка со слотом chart+legend забирает остаток высоты Container'а (гейт — только у Chart-контейнера). */
9144
+ ${({ $fill }) => $fill &&
9145
+ css `
9146
+ flex: 1 1 auto;
9147
+ min-height: 0;
9148
+ `}
9149
+
9150
+ > * {
9151
+ width: ${({ column }) => (column ? "100%" : "auto")};
9152
+ }
9153
+
9154
+ ${ContainerChart}, ${ContainerLegend} {
9155
+ width: ${({ column }) => (column ? "100%" : "50%")};
9156
+ }
9157
+
9158
+ ${ContainerLegend} {
9159
+ margin-left: ${({ column }) => (column ? 0 : "1rem")};
9160
+ }
9161
+
9162
+ ${ChartLegendContainer} {
9163
+ flex-direction: ${({ column }) => (column ? "row" : "column")};
9164
+ margin-top: ${({ column }) => (column ? "1rem" : 0)};
9165
+ }
9166
+ `;
9167
+ const ColorIconMixin = css `
9168
+ :after {
9169
+ color: ${({ $fontColor }) => $fontColor} !important;
9170
+ }
9171
+ `;
9172
+ const SizeIconMixin = css `
9173
+ :after {
9174
+ font-size: ${({ $fontSize }) => $fontSize}px !important;
9175
+ }
9176
+ `;
9177
+ styled(Icon).withConfig({ displayName: "ContainerIcon", componentId: "sc-1w9w0bq" }) `
9178
+ width: auto;
9179
+ height: auto;
9180
+ margin-bottom: 0.5rem;
9181
+ ${({ $fontColor }) => !!$fontColor && ColorIconMixin};
9182
+ ${({ $fontSize }) => !!$fontSize && SizeIconMixin};
9183
+ `;
9184
+ const SvgContainerColorMixin$1 = css `
9185
+ path,
9186
+ line,
9187
+ circle {
9188
+ fill: ${({ $fontColor }) => $fontColor};
9189
+ }
9190
+ `;
9191
+ const SvgContainer$1 = styled.div.withConfig({ displayName: "SvgContainer", componentId: "sc-1qs5gmx" }) `
9192
+ &&& {
9193
+ min-width: ${({ $width }) => ($width ? `${$width}px` : "1rem")};
9194
+ max-width: ${({ $width }) => ($width ? `${$width}px` : "1rem")};
9195
+
9196
+ ${({ $fontColor }) => !!$fontColor && SvgContainerColorMixin$1};
9197
+
9198
+ > * {
9199
+ min-width: inherit;
9200
+ }
9201
+ }
9202
+ `;
9203
+ const TwoColumnContainerWrapper = styled(Container).withConfig({ displayName: "TwoColumnContainerWrapper", componentId: "sc-1ogv4d3" }) `
9204
+ flex-direction: row;
9205
+ flex-wrap: nowrap;
9206
+ align-items: center;
9207
+
9208
+ > * {
9209
+ flex: 1;
9210
+ }
9211
+
9212
+ > ${ContainerValue} {
9213
+ justify-content: flex-end;
9214
+
9215
+ > * {
9216
+ text-align: right;
9217
+ }
9218
+ }
9219
+ `;
9220
+
9221
+ const CameraContainer = memo(({ elementConfig, type, renderElement }) => {
9222
+ const { expandedContainers } = useWidgetContext(type);
9223
+ const { id, options } = elementConfig || {};
9224
+ const { expandable, expanded } = options || {};
9225
+ const isVisible = isVisibleContainer(id, expandable, expanded, expandedContainers);
9226
+ const { root, body } = useContainerRoot({ elementConfig });
9227
+ return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsxs(Container, { ...body, children: [jsx(ContainerAlias, { hasBottomMargin: true, children: renderElement({ id: "alias" }) }), jsx(ContainerValue, { children: renderElement({ id: "value", wrap: false }) })] }))] }));
9228
+ });
9229
+
9230
+ /**
9231
+ * Контекст вписывания графика (`options.fill` контейнера Chart).
9232
+ *
9287
9233
  * Компонент `Chart` рендерится через `renderElement({ id: "chart" })` и получает только дочерний
9288
9234
  * узел `{ id: "chart" }` — опции контейнера до него не доходят. `ChartContainer` оборачивает
9289
9235
  * график в провайдер этого контекста, а `Chart` читает значение через `useContext`.
@@ -10919,6 +10865,61 @@ const LayersContainer = memo(({ type, elementConfig, renderElement }) => {
10919
10865
  return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsx(LayersContainerWrapper, { ...body, children: jsx(LayerTree, { layers: layers, onlyMainTools: true }) }))] }));
10920
10866
  });
10921
10867
 
10868
+ const useRenderContainer = ({ elementConfig, type, renderElement, renderBody, }) => {
10869
+ const { attributes } = useWidgetContext(type);
10870
+ const { getRenderContainerItem, attributesToRender } = useContainerAttributes({
10871
+ elementConfig,
10872
+ type,
10873
+ renderElement,
10874
+ });
10875
+ const renderContainer = useCallback((attribute) => {
10876
+ const item = getRenderContainerItem(elementConfig, attribute);
10877
+ const itemAttribute = attribute
10878
+ ? attributes?.find(({ attributeName }) => attributeName === attribute)
10879
+ : undefined;
10880
+ const overrideTemplateName = getDisplayTemplateNameFromAttribute(itemAttribute);
10881
+ if (overrideTemplateName) {
10882
+ const OverrideContainer = getContainerComponent(overrideTemplateName);
10883
+ if (OverrideContainer) {
10884
+ const itemConfig = {
10885
+ ...elementConfig,
10886
+ children: (elementConfig?.children ?? []).map(child => ({
10887
+ ...child,
10888
+ attributeName: attribute,
10889
+ })),
10890
+ templateName: overrideTemplateName,
10891
+ };
10892
+ return (jsx(OverrideContainer, { type: type, elementConfig: itemConfig, renderElement: item.render }, attribute));
10893
+ }
10894
+ }
10895
+ if (isEmptyElementValue(item.value) && item.hideEmpty)
10896
+ return null;
10897
+ return renderBody(item, attribute);
10898
+ }, [getRenderContainerItem, elementConfig, attributes, type, renderBody]);
10899
+ return { renderContainer, attributesToRender };
10900
+ };
10901
+
10902
+ const OneColumnContainer = memo(({ type, elementConfig, renderElement }) => {
10903
+ const { width, height, overflow } = elementConfig?.options || {};
10904
+ const templateName = elementConfig?.templateName;
10905
+ const bgHost = useBgImageHost(elementConfig);
10906
+ const renderBody = useCallback(({ id, value, style, hasUnits, render }) => (jsxs(Container, { id: id, isColumn: true, "data-templatename": templateName, style: style, "$sizeCss": getWrapperSizeStyle({
10907
+ style,
10908
+ width,
10909
+ height,
10910
+ overflow,
10911
+ defaults: BASE_CONTAINER_STYLE,
10912
+ defaultWidth: FILL_SIZE,
10913
+ }), ...bgHost, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: render }), jsxs(ContainerAlias, { hasBottomMargin: true, children: [render({ id: "alias" }), render({ id: "tooltip" }), render({ id: "modal" })] }), jsxs(ContainerValue, { children: [value, hasUnits && (jsx(ContainerUnits, { children: render({ id: "units" }) }))] })] })), [width, height, overflow, templateName, bgHost, elementConfig]);
10914
+ const { renderContainer, attributesToRender } = useRenderContainer({
10915
+ type,
10916
+ elementConfig,
10917
+ renderElement,
10918
+ renderBody,
10919
+ });
10920
+ return attributesToRender ? (jsx(Fragment$1, { children: attributesToRender.map(renderContainer) })) : (renderContainer());
10921
+ });
10922
+
10922
10923
  const PagesContainer = memo(({ type = WidgetType.Dashboard, noBorders }) => {
10923
10924
  const { config } = useWidgetConfig(type);
10924
10925
  const { pageIndex, currentPage } = useWidgetPage(type);
@@ -12087,6 +12088,734 @@ const UploadContainer = memo(({ type, elementConfig, renderElement }) => {
12087
12088
  return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsx(UploadContainerWrapper, { ...body, children: renderElement({ id: "uploader", wrap: false }) }))] }));
12088
12089
  });
12089
12090
 
12091
+ /** Зелёный для плашки «Ваш ответ принят». */
12092
+ const ACCEPTED_GREEN = "#2fa84f";
12093
+ const VoteWrapper = styled(Flex).withConfig({ displayName: "VoteWrapper", componentId: "sc-1cvc2lm" }) `
12094
+ flex-direction: column;
12095
+ gap: 0.75rem;
12096
+ width: 100%;
12097
+ color: ${({ theme: { palette } }) => palette.textPrimary};
12098
+ `;
12099
+ const VoteHeading = styled.div.withConfig({ displayName: "VoteHeading", componentId: "sc-rzdt4p" }) `
12100
+ font-size: 1rem;
12101
+ font-weight: 600;
12102
+ color: ${({ theme: { palette } }) => palette.textPrimary};
12103
+ `;
12104
+ const VoteQuestionText = styled.div.withConfig({ displayName: "VoteQuestionText", componentId: "sc-1t4w892" }) `
12105
+ font-size: 0.9375rem;
12106
+ font-weight: 600;
12107
+ color: ${({ theme: { palette } }) => palette.textPrimary};
12108
+ `;
12109
+ const VoteCategoryLabel = styled.div.withConfig({ displayName: "VoteCategoryLabel", componentId: "sc-z1fkgy" }) `
12110
+ font-size: 0.75rem;
12111
+ color: ${({ theme: { palette } }) => palette.textSecondary};
12112
+ `;
12113
+ const VoteSectionLabel = styled.div.withConfig({ displayName: "VoteSectionLabel", componentId: "sc-1xxrikh" }) `
12114
+ font-size: 0.75rem;
12115
+ color: ${({ theme: { palette } }) => palette.textSecondary};
12116
+ `;
12117
+ const VoteChipList = styled(Flex).withConfig({ displayName: "VoteChipList", componentId: "sc-1ahupix" }) `
12118
+ flex-wrap: wrap;
12119
+ gap: 0.375rem;
12120
+ `;
12121
+ const VoteChip = styled(Flex).withConfig({ displayName: "VoteChip", componentId: "sc-x1icg3" }) `
12122
+ align-items: center;
12123
+ gap: 0.25rem;
12124
+ padding: 0.25rem 0.5rem;
12125
+ background-color: ${({ theme: { palette } }) => palette.elementDark};
12126
+ border-radius: 0.25rem;
12127
+ font-size: 0.8125rem;
12128
+
12129
+ :hover {
12130
+ background-color: ${({ theme: { palette } }) => palette.elementDeep};
12131
+ }
12132
+ `;
12133
+ /**
12134
+ * Список вариантов ответа.
12135
+ *
12136
+ * Обёртка — именно `RadioGroup`: `Radio` из uilib читает его legacy-контекст и без него бросает
12137
+ * `Radio must be controlled with RadioGroup context.` — даже когда `checked` и `onChange` заданы
12138
+ * на самом контроле. Группа стоит всегда, а не только в режиме одиночного выбора: лишнего она не
12139
+ * рисует, зато контекст не может пропасть при смене экрана.
12140
+ *
12141
+ * Подпись у `Radio` своя, но текст варианта рисует строка — он делит место со счётчиком и
12142
+ * прогресс-баром. Поэтому пустой узел подписи и отступ кружка до него снимаются.
12143
+ */
12144
+ const VoteVariantList = styled(RadioGroup).withConfig({ displayName: "VoteVariantList", componentId: "sc-kie7qv" }) `
12145
+ width: 100%;
12146
+
12147
+ label > div {
12148
+ display: none;
12149
+ }
12150
+
12151
+ label > span {
12152
+ margin-right: 0;
12153
+ }
12154
+ `;
12155
+ const VoteVariantRow = styled.div.withConfig({ displayName: "VoteVariantRow", componentId: "sc-hlij7m" }) `
12156
+ display: flex;
12157
+ flex-direction: column;
12158
+ gap: 0.25rem;
12159
+ padding: 0.375rem 0;
12160
+ `;
12161
+ const VoteVariantHead = styled(Flex).withConfig({ displayName: "VoteVariantHead", componentId: "sc-1kbw26l" }) `
12162
+ align-items: center;
12163
+ justify-content: space-between;
12164
+ gap: 0.5rem;
12165
+ font-size: 0.875rem;
12166
+ `;
12167
+ const VoteVariantText = styled.span.withConfig({ displayName: "VoteVariantText", componentId: "sc-1el70dv" }) `
12168
+ flex: 1;
12169
+ min-width: 0;
12170
+ `;
12171
+ const VoteVariantCount = styled.span.withConfig({ displayName: "VoteVariantCount", componentId: "sc-jle8ey" }) `
12172
+ flex-shrink: 0;
12173
+ font-size: 0.8125rem;
12174
+ color: ${({ theme: { palette } }) => palette.textSecondary};
12175
+ `;
12176
+ const VoteAcceptedBadge = styled(Flex).withConfig({ displayName: "VoteAcceptedBadge", componentId: "sc-1utdte8" }) `
12177
+ align-items: center;
12178
+ justify-content: center;
12179
+ gap: 0.5rem;
12180
+ padding: 0.5rem 0.75rem;
12181
+ border-radius: 0.375rem;
12182
+ background-color: ${ACCEPTED_GREEN};
12183
+ color: #ffffff;
12184
+ font-size: 0.875rem;
12185
+ `;
12186
+ const VoteAuthHint = styled.div.withConfig({ displayName: "VoteAuthHint", componentId: "sc-l6fdf1" }) `
12187
+ font-size: 0.8125rem;
12188
+ color: ${({ theme: { palette } }) => palette.textSecondary};
12189
+ `;
12190
+ const VoteEditLink = styled.button.withConfig({ displayName: "VoteEditLink", componentId: "sc-1iedcup" }) `
12191
+ align-self: flex-start;
12192
+ padding: 0;
12193
+ border: none;
12194
+ background: none;
12195
+ cursor: pointer;
12196
+ font-size: 0.8125rem;
12197
+ color: ${({ theme: { palette } }) => palette.primary};
12198
+
12199
+ :hover {
12200
+ text-decoration: underline;
12201
+ }
12202
+ `;
12203
+ const VoteShareBlock = styled(Flex).withConfig({ displayName: "VoteShareBlock", componentId: "sc-a8sttg" }) `
12204
+ flex-direction: column;
12205
+ gap: 0.375rem;
12206
+ margin-top: 0.25rem;
12207
+ `;
12208
+ const VoteShareChips = styled(Flex).withConfig({ displayName: "VoteShareChips", componentId: "sc-1xnysrh" }) `
12209
+ flex-wrap: wrap;
12210
+ gap: 0.375rem;
12211
+ `;
12212
+ const VoteShareChip = styled.div.withConfig({ displayName: "VoteShareChip", componentId: "sc-mrpbcg" }) `
12213
+ padding: 0.25rem 0.625rem;
12214
+ border-radius: 1rem;
12215
+ background-color: ${({ theme: { palette } }) => palette.element};
12216
+ color: ${({ theme: { palette } }) => palette.textSecondary};
12217
+ font-size: 0.75rem;
12218
+ `;
12219
+ styled.div.withConfig({ displayName: "VoteError", componentId: "sc-1gfzwqo" }) `
12220
+ font-size: 0.8125rem;
12221
+ color: ${({ theme: { palette } }) => palette.error};
12222
+ `;
12223
+
12224
+ /** Имена полей таблиц голосования. */
12225
+ const VOTE_FIELDS = {
12226
+ category: {
12227
+ id: "id",
12228
+ name: "name",
12229
+ },
12230
+ question: {
12231
+ id: "id",
12232
+ text: "text",
12233
+ categoryId: "category_id",
12234
+ userName: "user_name",
12235
+ multiSelect: "multi_select",
12236
+ },
12237
+ variant: {
12238
+ id: "id",
12239
+ questionId: "question_id",
12240
+ text: "text",
12241
+ },
12242
+ answer: {
12243
+ id: "id",
12244
+ questionId: "question_id",
12245
+ userName: "user_name",
12246
+ variantId: "variant_id",
12247
+ },
12248
+ };
12249
+ /** Минимальное число вариантов ответа в голосовании. */
12250
+ const MIN_VARIANTS = 2;
12251
+ /** Лимит при чтении списков (вариантов/ответов/категорий). */
12252
+ const VOTE_FETCH_LIMIT = 10000;
12253
+ /** База для перевода доли голосов в проценты. */
12254
+ const PERCENT_BASE = 100;
12255
+ /** Namespace переводов потребляющего приложения (подписи берутся через `t` с defaultValue). */
12256
+ const VOTE_NS = "dashboard";
12257
+ /**
12258
+ * Имя публичного (анонимного) аккаунта портала.
12259
+ *
12260
+ * Под ним сервер отдаёт `getUserInfo()` любому неавторизованному посетителю шаренного проекта,
12261
+ * поэтому «кто уже голосовал» по нему не считается: первый же голос закрыл бы голосование всем
12262
+ * сразу. Такой посетитель для контейнера — неавторизованный, ему доступны только результаты.
12263
+ *
12264
+ * Признака анонима в API нет (`UserInfoDc` — это `username` + `roles`), поэтому имя приходится
12265
+ * знать по конвенции портала: оно же стоит ролью в ACL публичных ресурсов.
12266
+ */
12267
+ const PUBLIC_USER_NAME = "public_user";
12268
+ /** Социальные сети для блока «Поделиться». */
12269
+ const VOTE_SOCIALS = ["Telegram", "VK"];
12270
+
12271
+ /** Локальное состояние формы создания/редактирования голосования (категория, вопрос, чипсы-варианты). */
12272
+ const useVoteForm = (initialValues) => {
12273
+ const [categoryId, setCategoryId] = useState(initialValues?.categoryId ?? "");
12274
+ const [text, setText] = useState(initialValues?.text ?? "");
12275
+ const [multiSelect, setMultiSelect] = useState(initialValues?.multiSelect ?? false);
12276
+ const [variants, setVariants] = useState(initialValues?.variants ?? []);
12277
+ const [draft, setDraft] = useState("");
12278
+ const addVariant = useCallback(() => {
12279
+ const value = draft.trim();
12280
+ if (!value)
12281
+ return;
12282
+ setVariants(prev => [...prev, { text: value }]);
12283
+ setDraft("");
12284
+ }, [draft]);
12285
+ const removeVariant = useCallback((index) => {
12286
+ setVariants(prev => prev.filter((_, itemIndex) => itemIndex !== index));
12287
+ }, []);
12288
+ const isValid = useMemo(() => Boolean(categoryId && text.trim() && variants.length >= MIN_VARIANTS), [categoryId, text, variants.length]);
12289
+ const values = useMemo(() => ({ categoryId, text: text.trim(), multiSelect, variants }), [categoryId, text, multiSelect, variants]);
12290
+ return {
12291
+ categoryId,
12292
+ setCategoryId,
12293
+ text,
12294
+ setText,
12295
+ multiSelect,
12296
+ setMultiSelect,
12297
+ variants,
12298
+ addVariant,
12299
+ removeVariant,
12300
+ draft,
12301
+ setDraft,
12302
+ isValid,
12303
+ values,
12304
+ };
12305
+ };
12306
+
12307
+ const VoteCreateForm = ({ categories, initialValues, isEditing, canDelete, submitting, onSubmit, onDelete, onCancel, }) => {
12308
+ const { t } = useGlobalContext();
12309
+ const form = useVoteForm(initialValues);
12310
+ const { categoryId, setCategoryId, text, setText, multiSelect, setMultiSelect, variants, addVariant, removeVariant, draft, setDraft, isValid, values, } = form;
12311
+ const categoryOptions = useMemo(() => categories.map(({ id, name }) => ({ text: name, value: id })), [categories]);
12312
+ return (jsxs(VoteWrapper, { children: [!isEditing && jsx(VoteHeading, { children: t("vote.newVote", { ns: VOTE_NS, defaultValue: "Новое голосование" }) }), jsx(Dropdown, { zIndex: 110000, label: t("vote.category", { ns: VOTE_NS, defaultValue: "Категория" }), width: "100%", placeholder: t("vote.categoryPlaceholder", { ns: VOTE_NS, defaultValue: "Выберите категорию" }), options: categoryOptions, value: categoryId, onChange: items => setCategoryId(String(items[0]?.value ?? "")) }), jsx(Input, { label: t("vote.question", { ns: VOTE_NS, defaultValue: "Вопрос" }), width: "100%", placeholder: t("vote.questionPlaceholder", { ns: VOTE_NS, defaultValue: "Текст вопроса" }), value: text, onChange: ({ target }) => setText(target.value) }), jsx(VoteSectionLabel, { children: t("vote.variants", { ns: VOTE_NS, defaultValue: "Варианты ответов" }) }), !!variants.length && (jsx(VoteChipList, { children: variants.map(({ text: variantText }, index) => (jsxs(VoteChip, { children: [jsx(VoteVariantText, { children: variantText }), jsx(IconButton, { kind: "close", onClick: () => removeVariant(index) })] }, `${variantText}:${index}`))) })), jsx(Input, { width: "100%", placeholder: t("vote.variantPlaceholder", { ns: VOTE_NS, defaultValue: "Введите вариант и нажмите Enter" }), value: draft, onChange: ({ target }) => setDraft(target.value), onKeyDown: event => {
12313
+ if (event.key === "Enter") {
12314
+ event.preventDefault();
12315
+ addVariant();
12316
+ }
12317
+ } }), jsxs(Flex, { alignItems: "center", justifyContent: "space-between", children: [jsx(VoteSectionLabel, { children: t("vote.multiSelect", { ns: VOTE_NS, defaultValue: "Множественный выбор" }) }), jsx(Switch, { checked: multiSelect, onChange: () => setMultiSelect(prev => !prev) })] }), jsx(RaisedButton, { primary: true, disabled: !isValid || submitting, onClick: () => onSubmit(values), children: isEditing
12318
+ ? t("vote.save", { ns: VOTE_NS, defaultValue: "Сохранить" })
12319
+ : t("vote.create", { ns: VOTE_NS, defaultValue: "Создать голосование" }) }), isEditing && (jsxs(Flex, { alignItems: "center", justifyContent: "space-between", children: [onCancel && (jsx(VoteEditLink, { onClick: onCancel, children: t("vote.cancel", { ns: VOTE_NS, defaultValue: "Отмена" }) })), canDelete && onDelete && (jsx(RaisedButton, { error: true, disabled: submitting, onClick: onDelete, children: t("vote.delete", { ns: VOTE_NS, defaultValue: "Удалить голосование" }) }))] }))] }));
12320
+ };
12321
+
12322
+ /** Блок «Поделиться в соцсетях» — пока только вёрстка, без рабочих ссылок. */
12323
+ const VoteShare = () => {
12324
+ const { t } = useGlobalContext();
12325
+ return (jsxs(VoteShareBlock, { children: [jsx(VoteSectionLabel, { children: t("vote.share", { ns: VOTE_NS, defaultValue: "Поделиться в соцсетях" }) }), jsx(VoteShareChips, { children: VOTE_SOCIALS.map(name => (jsx(VoteShareChip, { children: name }, name))) })] }));
12326
+ };
12327
+
12328
+ const VoteResults = ({ question, category, results, screen, isOwner, hasAnswers, submitting, onSubmit, onEdit, }) => {
12329
+ const { t } = useGlobalContext();
12330
+ const [selected, setSelected] = useState([]);
12331
+ const showControls = screen === "voting";
12332
+ const showShare = screen === "voting" || screen === "voted";
12333
+ const canEdit = screen === "voting" && isOwner && !hasAnswers;
12334
+ const toggle = useCallback((variantId) => {
12335
+ setSelected(prev => {
12336
+ if (!question.multiSelect)
12337
+ return [variantId];
12338
+ return prev.includes(variantId) ? prev.filter(id => id !== variantId) : [...prev, variantId];
12339
+ });
12340
+ }, [question.multiSelect]);
12341
+ const canSubmit = useMemo(() => selected.length > 0 && !submitting, [selected.length, submitting]);
12342
+ return (jsxs(VoteWrapper, { children: [category && jsx(VoteCategoryLabel, { children: category.name }), jsx(VoteQuestionText, { children: question.text }), jsx(VoteVariantList, { column: true, value: selected[0] ?? "", children: results.map(({ id, text, count, percent }) => (jsxs(VoteVariantRow, { children: [jsxs(VoteVariantHead, { children: [showControls &&
12343
+ (question.multiSelect ? (jsx(Checkbox, { checked: selected.includes(id), onChange: () => toggle(id) })) : (jsx(Radio, { value: id, checked: selected.includes(id), onChange: () => toggle(id) }))), jsx(VoteVariantText, { children: text }), jsx(VoteVariantCount, { children: count })] }), jsx(LinearProgress, { done: percent })] }, id))) }), screen === "unauthenticated" && (jsx(VoteAuthHint, { children: t("vote.authHint", { ns: VOTE_NS, defaultValue: "Чтобы проголосовать, пожалуйста, авторизуйтесь" }) })), screen === "voted" && (jsxs(VoteAcceptedBadge, { children: [jsx(Icon, { kind: "success" }), t("vote.accepted", { ns: VOTE_NS, defaultValue: "Ваш ответ принят" })] })), showControls && (jsx(RaisedButton, { primary: true, disabled: !canSubmit, onClick: () => onSubmit(selected), children: t("vote.submit", { ns: VOTE_NS, defaultValue: "Отправить ответ" }) })), canEdit && onEdit && (jsx(Flex, { children: jsx(VoteEditLink, { onClick: onEdit, children: t("vote.edit", { ns: VOTE_NS, defaultValue: "Редактировать" }) }) })), showShare && jsx(VoteShare, {})] }));
12344
+ };
12345
+
12346
+ /**
12347
+ * Тело контейнера: выбирает экран по вью-модели голосования.
12348
+ *
12349
+ * Живёт отдельно от `VoteContainer`, потому что тот отвечает за корень контейнера — размеры,
12350
+ * фон и заголовок — и не должен раздуваться ветвлением по состояниям.
12351
+ */
12352
+ const VoteScreen = ({ screen, categories, question, category, results, totalVotes, isOwner, hasAnswers, isEditing, setIsEditing, editInitialValues, submitting, createVote, submitAnswer, editVote, deleteVote, }) => {
12353
+ const handleCreate = useCallback((values) => void createVote(values), [createVote]);
12354
+ const handleEdit = useCallback((values) => void editVote(values).then(() => setIsEditing(false)), [editVote, setIsEditing]);
12355
+ const handleDelete = useCallback(() => void deleteVote().then(() => setIsEditing(false)), [deleteVote, setIsEditing]);
12356
+ const handleCancel = useCallback(() => setIsEditing(false), [setIsEditing]);
12357
+ const handleEditOpen = useCallback(() => setIsEditing(true), [setIsEditing]);
12358
+ const handleSubmitAnswer = useCallback((variantIds) => void submitAnswer(variantIds), [submitAnswer]);
12359
+ if (screen === "loading")
12360
+ return jsx(ContainerLoading, {});
12361
+ if (isEditing && question) {
12362
+ return (jsx(VoteCreateForm, { categories: categories, initialValues: editInitialValues, isEditing: true, canDelete: !hasAnswers, submitting: submitting, onSubmit: handleEdit, onDelete: handleDelete, onCancel: handleCancel }));
12363
+ }
12364
+ if (screen === "create") {
12365
+ return (jsx(VoteCreateForm, { categories: categories, isEditing: false, canDelete: false, submitting: submitting, onSubmit: handleCreate }));
12366
+ }
12367
+ if (!question)
12368
+ return null;
12369
+ return (jsx(VoteResults, { question: question, category: category, results: results, totalVotes: totalVotes, screen: screen, isOwner: isOwner, hasAnswers: hasAnswers, submitting: submitting, onSubmit: handleSubmitAnswer, onEdit: handleEditOpen }));
12370
+ };
12371
+
12372
+ /** Приводит значение атрибута к строке-идентификатору (id приходят числами/строками). */
12373
+ const toId = (value) => (value === null || value === undefined ? "" : String(value));
12374
+ /**
12375
+ * Условие равенства по числовому полю для `conditions` слоя: `field == value`.
12376
+ *
12377
+ * Двойное равенство — форма, которой написаны условия во всех конфигах проекта; лексер языка
12378
+ * условий принимает и одиночное `=`, но расходиться с авторскими конфигами незачем.
12379
+ */
12380
+ const eqCondition = (field, value) => `${field} == ${value}`;
12381
+ const props = (feature) => feature.properties ?? {};
12382
+ const mapCategories = (features) => features.map(feature => ({
12383
+ id: toId(props(feature)[VOTE_FIELDS.category.id] ?? feature.id),
12384
+ name: toId(props(feature)[VOTE_FIELDS.category.name]),
12385
+ }));
12386
+ const mapQuestion = (feature) => {
12387
+ const featureProps = props(feature);
12388
+ return {
12389
+ id: toId(featureProps[VOTE_FIELDS.question.id] ?? feature.id),
12390
+ text: toId(featureProps[VOTE_FIELDS.question.text]),
12391
+ categoryId: toId(featureProps[VOTE_FIELDS.question.categoryId]),
12392
+ userName: toId(featureProps[VOTE_FIELDS.question.userName]),
12393
+ multiSelect: Boolean(featureProps[VOTE_FIELDS.question.multiSelect]),
12394
+ };
12395
+ };
12396
+ const mapVariants = (features) => features.map(feature => ({
12397
+ id: toId(props(feature)[VOTE_FIELDS.variant.id] ?? feature.id),
12398
+ text: toId(props(feature)[VOTE_FIELDS.variant.text]),
12399
+ }));
12400
+ /** Список `variant_id` из ответов конкретного пользователя. */
12401
+ const getUserVariantIds = (answers, userName) => answers
12402
+ .filter(answer => toId(props(answer)[VOTE_FIELDS.answer.userName]) === userName)
12403
+ .map(answer => toId(props(answer)[VOTE_FIELDS.answer.variantId]));
12404
+ /** Id записей-ответов конкретного пользователя (для замены ответа при переголосовании). */
12405
+ const getUserAnswerIds = (answers, userName) => answers
12406
+ .filter(answer => toId(props(answer)[VOTE_FIELDS.answer.userName]) === userName)
12407
+ .map(answer => toId(answer.id))
12408
+ .filter(Boolean);
12409
+ /**
12410
+ * Считает голоса по вариантам. `percent` — доля голосов варианта от общего числа голосов
12411
+ * по голосованию.
12412
+ */
12413
+ const computeResults = (variants, answers) => {
12414
+ const counts = new Map();
12415
+ answers.forEach(answer => {
12416
+ const variantId = toId(props(answer)[VOTE_FIELDS.answer.variantId]);
12417
+ counts.set(variantId, (counts.get(variantId) ?? 0) + 1);
12418
+ });
12419
+ const totalVotes = answers.length;
12420
+ const results = variants.map(variant => {
12421
+ const count = counts.get(variant.id) ?? 0;
12422
+ return {
12423
+ ...variant,
12424
+ count,
12425
+ percent: totalVotes ? Math.round((count / totalVotes) * PERCENT_BASE) : 0,
12426
+ };
12427
+ });
12428
+ return { results, totalVotes };
12429
+ };
12430
+ /**
12431
+ * Сравнивает варианты из формы с существующими в БД и раскладывает на наборы операций:
12432
+ * создать (новые без id), обновить (изменился текст) и удалить (исчезли из формы).
12433
+ */
12434
+ const diffVariants = (existing, next) => {
12435
+ const nextIds = new Set(next.filter(({ id }) => id).map(({ id }) => id));
12436
+ const existingById = new Map(existing.map(variant => [variant.id, variant]));
12437
+ const toCreate = next.filter(({ id, text }) => !id && text.trim()).map(({ text }) => text.trim());
12438
+ const toUpdate = next
12439
+ .filter(({ id, text }) => id && existingById.get(id) && existingById.get(id)?.text !== text.trim())
12440
+ .map(({ id, text }) => ({ id: id, text: text.trim() }));
12441
+ const toDeleteIds = existing.filter(({ id }) => !nextIds.has(id)).map(({ id }) => id);
12442
+ return { toCreate, toUpdate, toDeleteIds };
12443
+ };
12444
+
12445
+ /**
12446
+ * Операции записи контейнера: создание голосования, отправка ответа, редактирование и удаление.
12447
+ * Все операции доступны только авторизованному пользователю.
12448
+ */
12449
+ const useVoteActions = ({ dataSources, attributeName, featureId, featureLayerName, username, question, variants, answers, onQuestionIdChange, reload, }) => {
12450
+ const { api } = useGlobalContext();
12451
+ const { questionDataSource, variantDataSource, answerDataSource } = dataSources;
12452
+ const [submitting, setSubmitting] = useState(false);
12453
+ const writeFeatureAttribute = useCallback(async (value) => {
12454
+ if (!api || !featureLayerName || !featureId || !attributeName)
12455
+ return;
12456
+ await api.layers.updateFeature(featureLayerName, [{ id: featureId, attributes: { [attributeName]: value } }]);
12457
+ }, [api, featureLayerName, featureId, attributeName]);
12458
+ const createVote = useCallback(async (values) => {
12459
+ if (!api)
12460
+ return;
12461
+ setSubmitting(true);
12462
+ try {
12463
+ const questionResult = await api.layers.createFeatures({ name: questionDataSource }, [
12464
+ {
12465
+ properties: {
12466
+ [VOTE_FIELDS.question.text]: values.text,
12467
+ [VOTE_FIELDS.question.categoryId]: values.categoryId,
12468
+ [VOTE_FIELDS.question.userName]: username,
12469
+ [VOTE_FIELDS.question.multiSelect]: values.multiSelect,
12470
+ },
12471
+ },
12472
+ ]);
12473
+ const newQuestionId = questionResult.createdIds?.[0];
12474
+ if (!newQuestionId)
12475
+ return;
12476
+ await api.layers.createFeatures({ name: variantDataSource }, values.variants
12477
+ .filter(({ text }) => text.trim())
12478
+ .map(({ text }) => ({
12479
+ properties: {
12480
+ [VOTE_FIELDS.variant.questionId]: newQuestionId,
12481
+ [VOTE_FIELDS.variant.text]: text.trim(),
12482
+ },
12483
+ })));
12484
+ await writeFeatureAttribute(newQuestionId);
12485
+ onQuestionIdChange(newQuestionId);
12486
+ reload();
12487
+ }
12488
+ finally {
12489
+ setSubmitting(false);
12490
+ }
12491
+ }, [api, questionDataSource, variantDataSource, username, writeFeatureAttribute, onQuestionIdChange, reload]);
12492
+ const submitAnswer = useCallback(async (variantIds) => {
12493
+ if (!api || !question || !variantIds.length)
12494
+ return;
12495
+ setSubmitting(true);
12496
+ try {
12497
+ // Заложено на будущее (переголосование без UI): сначала снимаем прежние ответы пользователя.
12498
+ const previousAnswerIds = getUserAnswerIds(answers, username);
12499
+ if (previousAnswerIds.length) {
12500
+ await api.layers.deleteFeatures({ name: answerDataSource, ids: previousAnswerIds });
12501
+ }
12502
+ await api.layers.createFeatures({ name: answerDataSource }, variantIds.map(variantId => ({
12503
+ properties: {
12504
+ [VOTE_FIELDS.answer.questionId]: question.id,
12505
+ [VOTE_FIELDS.answer.userName]: username,
12506
+ [VOTE_FIELDS.answer.variantId]: variantId,
12507
+ },
12508
+ })));
12509
+ reload();
12510
+ }
12511
+ finally {
12512
+ setSubmitting(false);
12513
+ }
12514
+ }, [api, question, answers, username, answerDataSource, reload]);
12515
+ const editVote = useCallback(async (values) => {
12516
+ if (!api || !question)
12517
+ return;
12518
+ setSubmitting(true);
12519
+ try {
12520
+ await api.layers.updateFeature(questionDataSource, [
12521
+ {
12522
+ id: question.id,
12523
+ attributes: {
12524
+ [VOTE_FIELDS.question.text]: values.text,
12525
+ [VOTE_FIELDS.question.categoryId]: values.categoryId,
12526
+ [VOTE_FIELDS.question.multiSelect]: values.multiSelect,
12527
+ },
12528
+ },
12529
+ ]);
12530
+ const { toCreate, toUpdate, toDeleteIds } = diffVariants(variants, values.variants);
12531
+ if (toCreate.length) {
12532
+ await api.layers.createFeatures({ name: variantDataSource }, toCreate.map(text => ({
12533
+ properties: { [VOTE_FIELDS.variant.questionId]: question.id, [VOTE_FIELDS.variant.text]: text },
12534
+ })));
12535
+ }
12536
+ if (toUpdate.length) {
12537
+ await api.layers.updateFeature(variantDataSource, toUpdate.map(({ id, text }) => ({ id, attributes: { [VOTE_FIELDS.variant.text]: text } })));
12538
+ }
12539
+ if (toDeleteIds.length) {
12540
+ await api.layers.deleteFeatures({ name: variantDataSource, ids: toDeleteIds });
12541
+ }
12542
+ reload();
12543
+ }
12544
+ finally {
12545
+ setSubmitting(false);
12546
+ }
12547
+ }, [api, question, questionDataSource, variantDataSource, variants, reload]);
12548
+ const deleteVote = useCallback(async () => {
12549
+ if (!api || !question)
12550
+ return;
12551
+ setSubmitting(true);
12552
+ try {
12553
+ const variantIds = variants.map(({ id }) => id).filter(Boolean);
12554
+ if (variantIds.length) {
12555
+ await api.layers.deleteFeatures({ name: variantDataSource, ids: variantIds });
12556
+ }
12557
+ await api.layers.deleteFeature({ name: questionDataSource, id: question.id });
12558
+ await writeFeatureAttribute(null);
12559
+ onQuestionIdChange("");
12560
+ reload();
12561
+ }
12562
+ finally {
12563
+ setSubmitting(false);
12564
+ }
12565
+ }, [api, question, variants, variantDataSource, questionDataSource, writeFeatureAttribute, onQuestionIdChange, reload]);
12566
+ return { submitting, createVote, submitAnswer, editVote, deleteVote };
12567
+ };
12568
+
12569
+ /**
12570
+ * Загружает справочник категорий, а по `questionId` — само голосование, его варианты и ответы.
12571
+ * Перечитывается при смене `questionId` или `reloadToken` (после создания/голосования/правки).
12572
+ */
12573
+ const useVoteData = ({ dataSources, questionId, reloadToken }) => {
12574
+ const { api } = useGlobalContext();
12575
+ const { categoryDataSource, questionDataSource, variantDataSource, answerDataSource } = dataSources;
12576
+ const [categories, setCategories] = useState([]);
12577
+ const [question, setQuestion] = useState(null);
12578
+ const [variants, setVariants] = useState([]);
12579
+ const [answers, setAnswers] = useState([]);
12580
+ const [loading, setLoading] = useState(false);
12581
+ const [error, setError] = useState(false);
12582
+ useEffect(() => {
12583
+ let active = true;
12584
+ if (api && categoryDataSource) {
12585
+ api.layers
12586
+ .getFeatures(categoryDataSource, { withGeom: false, limit: VOTE_FETCH_LIMIT })
12587
+ .then(response => {
12588
+ if (active)
12589
+ setCategories(mapCategories(response.features ?? []));
12590
+ })
12591
+ .catch(() => undefined);
12592
+ }
12593
+ return () => {
12594
+ active = false;
12595
+ };
12596
+ }, [api, categoryDataSource]);
12597
+ useEffect(() => {
12598
+ let active = true;
12599
+ if (!api || !questionId) {
12600
+ setQuestion(null);
12601
+ setVariants([]);
12602
+ setAnswers([]);
12603
+ return () => {
12604
+ active = false;
12605
+ };
12606
+ }
12607
+ setLoading(true);
12608
+ setError(false);
12609
+ Promise.all([
12610
+ api.layers.getFeatures(questionDataSource, {
12611
+ conditions: [eqCondition(VOTE_FIELDS.question.id, questionId)],
12612
+ withGeom: false,
12613
+ limit: 1,
12614
+ }),
12615
+ api.layers.getFeatures(variantDataSource, {
12616
+ conditions: [eqCondition(VOTE_FIELDS.variant.questionId, questionId)],
12617
+ withGeom: false,
12618
+ limit: VOTE_FETCH_LIMIT,
12619
+ }),
12620
+ api.layers.getFeatures(answerDataSource, {
12621
+ conditions: [eqCondition(VOTE_FIELDS.answer.questionId, questionId)],
12622
+ withGeom: false,
12623
+ limit: VOTE_FETCH_LIMIT,
12624
+ }),
12625
+ ])
12626
+ .then(([questionResponse, variantResponse, answerResponse]) => {
12627
+ if (!active)
12628
+ return;
12629
+ const questionFeature = questionResponse.features?.[0];
12630
+ setQuestion(questionFeature ? mapQuestion(questionFeature) : null);
12631
+ setVariants(mapVariants(variantResponse.features ?? []));
12632
+ setAnswers(answerResponse.features ?? []);
12633
+ })
12634
+ .catch(() => {
12635
+ if (active)
12636
+ setError(true);
12637
+ })
12638
+ .finally(() => {
12639
+ if (active)
12640
+ setLoading(false);
12641
+ });
12642
+ return () => {
12643
+ active = false;
12644
+ };
12645
+ }, [api, questionId, reloadToken, questionDataSource, variantDataSource, answerDataSource]);
12646
+ const { results, totalVotes } = useMemo(() => computeResults(variants, answers), [variants, answers]);
12647
+ return { categories, question, variants, answers, results, totalVotes, loading, error };
12648
+ };
12649
+
12650
+ /**
12651
+ * Текущий пользователь для контейнера голосования. Авторизация обязательна — неавторизованный
12652
+ * пользователь видит только результаты. Источник — `api.account.getUserInfo()`; ошибка/отсутствие
12653
+ * `username` трактуются как «не авторизован».
12654
+ *
12655
+ * Публичный аккаунт портала (`PUBLIC_USER_NAME` в `username` или в ролях) авторизацией не
12656
+ * считается: под ним ходят ВСЕ анонимные посетители шаренного проекта, поэтому и «мой голос»,
12657
+ * и владение голосованием опознались бы у них общими на всех.
12658
+ */
12659
+ const useVoteUser = () => {
12660
+ const { api } = useGlobalContext();
12661
+ const [username, setUsername] = useState("");
12662
+ const [roles, setRoles] = useState([]);
12663
+ const [loading, setLoading] = useState(true);
12664
+ useEffect(() => {
12665
+ let active = true;
12666
+ if (!api) {
12667
+ setLoading(false);
12668
+ return () => {
12669
+ active = false;
12670
+ };
12671
+ }
12672
+ api.account
12673
+ .getUserInfo()
12674
+ .then(info => {
12675
+ if (!active)
12676
+ return;
12677
+ setUsername(info?.username ?? "");
12678
+ setRoles(info?.roles ?? []);
12679
+ })
12680
+ .catch(() => {
12681
+ if (!active)
12682
+ return;
12683
+ setUsername("");
12684
+ setRoles([]);
12685
+ })
12686
+ .finally(() => {
12687
+ if (active)
12688
+ setLoading(false);
12689
+ });
12690
+ return () => {
12691
+ active = false;
12692
+ };
12693
+ }, [api]);
12694
+ const isPublic = useMemo(() => username === PUBLIC_USER_NAME || roles.includes(PUBLIC_USER_NAME), [username, roles]);
12695
+ const isAuthenticated = !!username && !isPublic;
12696
+ return { username, isAuthenticated, loading };
12697
+ };
12698
+
12699
+ const normalizeAttributeName = (attributeName) => {
12700
+ if (Array.isArray(attributeName))
12701
+ return attributeName[0] ?? "";
12702
+ return attributeName ?? "";
12703
+ };
12704
+ /**
12705
+ * Вью-модель контейнера голосования: определяет текущий экран и собирает данные/действия.
12706
+ * Привязка к объекту карты — через `elementConfig.attributeName` (хранит `question_id`).
12707
+ */
12708
+ const useVoteContainer = (elementConfig) => {
12709
+ const { feature, layerInfo } = useWidgetContext(WidgetType.FeatureCard);
12710
+ const options = elementConfig?.options;
12711
+ const attributeName = normalizeAttributeName(elementConfig?.attributeName);
12712
+ const dataSources = useMemo(() => ({
12713
+ categoryDataSource: options?.categoryDataSource ?? "",
12714
+ questionDataSource: options?.questionDataSource ?? "",
12715
+ variantDataSource: options?.variantDataSource ?? "",
12716
+ answerDataSource: options?.answerDataSource ?? "",
12717
+ }), [options]);
12718
+ const configured = useMemo(() => Boolean(attributeName &&
12719
+ dataSources.categoryDataSource &&
12720
+ dataSources.questionDataSource &&
12721
+ dataSources.variantDataSource &&
12722
+ dataSources.answerDataSource), [attributeName, dataSources]);
12723
+ const { username, isAuthenticated, loading: userLoading } = useVoteUser();
12724
+ const featureId = feature?.id;
12725
+ const featureQuestionId = toId(feature?.properties?.[attributeName]);
12726
+ const [overrideQuestionId, setOverrideQuestionId] = useState(null);
12727
+ useEffect(() => {
12728
+ setOverrideQuestionId(null);
12729
+ }, [featureId]);
12730
+ const questionId = overrideQuestionId ?? featureQuestionId;
12731
+ const [reloadToken, setReloadToken] = useState(0);
12732
+ const reload = useCallback(() => setReloadToken(token => token + 1), []);
12733
+ const onQuestionIdChange = useCallback((id) => setOverrideQuestionId(id), []);
12734
+ const { categories, question, variants, answers, results, totalVotes, loading } = useVoteData({
12735
+ dataSources,
12736
+ questionId,
12737
+ reloadToken,
12738
+ });
12739
+ const [isEditing, setIsEditing] = useState(false);
12740
+ const actions = useVoteActions({
12741
+ dataSources,
12742
+ attributeName,
12743
+ featureId,
12744
+ featureLayerName: layerInfo?.name,
12745
+ username,
12746
+ question,
12747
+ variants,
12748
+ answers,
12749
+ onQuestionIdChange,
12750
+ reload,
12751
+ });
12752
+ const hasAnswers = answers.length > 0;
12753
+ const isOwner = isAuthenticated && !!question && question.userName === username;
12754
+ const hasVoted = useMemo(() => getUserVariantIds(answers, username).length > 0, [answers, username]);
12755
+ const category = useMemo(() => categories.find(({ id }) => id === question?.categoryId), [categories, question?.categoryId]);
12756
+ const screen = useMemo(() => {
12757
+ if (!configured)
12758
+ return null;
12759
+ if (userLoading)
12760
+ return "loading";
12761
+ if (!questionId)
12762
+ return isAuthenticated ? "create" : null;
12763
+ if (loading && !question)
12764
+ return "loading";
12765
+ if (!question)
12766
+ return null;
12767
+ if (!isAuthenticated)
12768
+ return "unauthenticated";
12769
+ return hasVoted ? "voted" : "voting";
12770
+ }, [configured, userLoading, questionId, isAuthenticated, loading, question, hasVoted]);
12771
+ const editInitialValues = useMemo(() => question
12772
+ ? {
12773
+ categoryId: question.categoryId,
12774
+ text: question.text,
12775
+ multiSelect: question.multiSelect,
12776
+ variants: variants.map(({ id, text }) => ({ id, text })),
12777
+ }
12778
+ : undefined, [question, variants]);
12779
+ return {
12780
+ configured,
12781
+ screen,
12782
+ categories,
12783
+ question,
12784
+ category,
12785
+ results,
12786
+ totalVotes,
12787
+ isOwner,
12788
+ hasAnswers,
12789
+ isEditing,
12790
+ setIsEditing,
12791
+ editInitialValues,
12792
+ ...actions,
12793
+ };
12794
+ };
12795
+
12796
+ /**
12797
+ * Контейнер «Голосование»: привязывает опрос к объекту карты через `attributeName`
12798
+ * и показывает один из экранов — создание, голосование, после голосования
12799
+ * или (для неавторизованных) только результаты.
12800
+ *
12801
+ * Корнем владеет сам (`useContainerRoot`), поэтому шаблон записан в `ROOT_OWNING_TEMPLATES`:
12802
+ * размеры, авторский `style`, фон и заголовок живут на одном узле, без внешней обёртки.
12803
+ */
12804
+ const VoteContainer = memo(({ type, elementConfig, isVisible, renderElement }) => {
12805
+ const { root, body } = useContainerRoot({ elementConfig });
12806
+ const { configured, ...vote } = useVoteContainer(elementConfig);
12807
+ // Без имени атрибута или хотя бы одного источника контейнеру не с чем работать: раньше он
12808
+ // молча возвращал null, и опечатка в конфиге выглядела как «блока просто нет».
12809
+ if (!configured) {
12810
+ return jsx(DataSourceError, { name: elementConfig?.templateName ?? ContainerTemplate.Vote });
12811
+ }
12812
+ // Голосования нет, а создавать его посетитель не вправе — показывать нечего. Пустой заголовок
12813
+ // в карточке в этом случае тоже не нужен, поэтому не рисуем и корень.
12814
+ if (!vote.screen)
12815
+ return null;
12816
+ return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsx(Container, { ...body, isColumn: true, children: jsx(VoteScreen, { ...vote, screen: vote.screen }) }))] }));
12817
+ });
12818
+
12090
12819
  const ExpandableTitle = memo(({ elementConfig, type, renderElement }) => {
12091
12820
  const { expandedContainers } = useWidgetContext(type);
12092
12821
  const { id, options, templateName, children } = elementConfig || {};
@@ -13417,6 +14146,7 @@ const createContainerComponents = () => ({
13417
14146
  [ContainerTemplate.EditAttachment]: EditAttachmentContainer,
13418
14147
  [ContainerTemplate.Attachment]: AttachmentContainer,
13419
14148
  [ContainerTemplate.EditGroup]: EditGroupContainer,
14149
+ [ContainerTemplate.Vote]: VoteContainer,
13420
14150
  [ContainerTemplate.ContainersGroup]: ContainersGroupContainer,
13421
14151
  [ContainerTemplate.GridRow]: GridRowContainer,
13422
14152
  [ContainerTemplate.StructuredData]: StructuredDataContainer,
@@ -13452,6 +14182,7 @@ const ROOT_OWNING_TEMPLATES = new Set([
13452
14182
  ContainerTemplate.StructuredData,
13453
14183
  ContainerTemplate.Task,
13454
14184
  ContainerTemplate.Upload,
14185
+ ContainerTemplate.Vote,
13455
14186
  ]);
13456
14187
  let cachedContainerComponents = null;
13457
14188
  const getContainerComponents = () => {
@@ -18520,5 +19251,5 @@ const DEFAULT_HEATMAP_STYLE = {
18520
19251
  ],
18521
19252
  };
18522
19253
 
18523
- export { ALIGNMENTS, ALIGN_ITEMS, ATTRIBUTE_ICON_ELEMENT_TYPES, AddButtonRow, AddFeatureButton, AddFeatureContainer, AlertIconContainer, AttachmentContainer, AttributeGalleryContainer, AttributeLabel, BASE_CONTAINER_STYLE, BG_IMAGE_SLOT_ID, BaseMapTheme, CHART_TYPES, COMPACT_FRACTION_DIGITS, CONFIG_PAGES_ID, CONFIG_PAGE_ID, CONTAINERS_GROUP_DEFAULTS, CONTAINER_BODY_ATTRIBUTE, CONTAINER_BODY_FILL_STYLE, CameraContainer, Chart, ChartContainer, ChartLegend, ChartLoading, Container, ContainerBackground, ContainerChildren, ContainerLoading, ContainerRoot, ContainerTemplate, ContainerWrapper, ContainersGroupContainer, DASHBOARD_OVERLAY_Z_INDEX, DEFAULT_ATTRIBUTE_NAME, DEFAULT_BARCHART_RADIUS, DEFAULT_BASE_MAP, DEFAULT_BLUR, DEFAULT_CHART_ANGLE, DEFAULT_CHART_HEIGHT, DEFAULT_CHART_WIDTH, DEFAULT_CIRCLE_RADIUS, DEFAULT_CIRCLE_STROKE_WIDTH, DEFAULT_CIRCLE_STYLE, DEFAULT_COLOR, DEFAULT_DASHBOARD_CONFIG, DEFAULT_DATA_SOURCE_LIMIT, DEFAULT_DROPDOWN_WIDTH, DEFAULT_FILL_EXTRUSION_BASE, DEFAULT_FILL_EXTRUSION_HEIGHT, DEFAULT_FILL_EXTRUSION_STYLE, DEFAULT_FILL_EXTRUSION_VERTICAL_GRADIENT, DEFAULT_FILL_STYLE, DEFAULT_FILTER_PADDING, DEFAULT_GRID_GAP, DEFAULT_HEATMAP_COLOR, DEFAULT_HEATMAP_INTENSITY, DEFAULT_HEATMAP_RADIUS, DEFAULT_HEATMAP_STYLE, DEFAULT_HEATMAP_WEIGHT, DEFAULT_ICON_ANCHOR, DEFAULT_ICON_OVERLAP, DEFAULT_ICON_PADDING, DEFAULT_ICON_ROTATE, DEFAULT_ICON_SIZE, DEFAULT_ID_ATTRIBUTE_NAME, DEFAULT_LAT, DEFAULT_LINE_CAP, DEFAULT_LINE_JOIN, DEFAULT_LINE_STYLE, DEFAULT_LINE_WIDTH, DEFAULT_LNG, DEFAULT_OPACITY, DEFAULT_PAGES_CONFIG, DEFAULT_PIECHART_RADIUS, DEFAULT_SYMBOL_COLOR, DEFAULT_SYMBOL_HALO_COLOR, DEFAULT_SYMBOL_PLACEMENT, DEFAULT_SYMBOL_SPACING, DEFAULT_SYMBOL_STYLE, DEFAULT_TEXT_ANCHOR, DEFAULT_TEXT_JUSTIFY, DEFAULT_TEXT_SIZE, DEFAULT_TEXT_TRANSFORM, DEFAULT_TRACK_FR, DEFAULT_TRANSLATE, DEFAULT_ZOOM, DRAG_THRESHOLD_PX, Dashboard, DashboardCheckbox, DashboardChip, DashboardContent, DashboardContext, DashboardDefaultHeader, DashboardHeader, DashboardLoading, DashboardPlaceholder, DashboardPlaceholderWrap, DashboardProvider, DashboardWrapper, DataSourceContainer, DataSourceError, DataSourceErrorContainer, DataSourceInnerContainer, DataSourceProgressContainer, DateFormat, DefaultAttributesContainer, DefaultHeaderContainer, DefaultHeaderWrapper, DividerContainer, EMPTY_DATA_SOURCE_LAYER_INFO, EXTENT_FILTER_NAME, EditGeometryType, ElementButton, ElementCamera, ElementChart, ElementChips, ElementControl, ElementIcon, ElementImage, ElementLegend, ElementLink, ElementMarkdown, ElementSlideshow, ElementSvg, ElementTable, ElementTooltip, ElementValueWrapper, ExpandableTitle, FEATURE_CARD_DEFAULT_COLORS, FEATURE_CARD_OTHER_COLOR, FILL_SIZE, FILTERED_VALUE_OPACITY, FILTER_PREFIX, FR_PRECISION, FeatureCardBackgroundHeader, FeatureCardButtons, FeatureCardContext, FeatureCardDefaultHeader, FeatureCardHeader, FeatureCardProvider, FeatureCardSlideshowHeader, FeatureCardTitle, FeatureControls, FeatureTitleContainer, FiltersContainer, GEOMETRY_ATTRIBUTE, GEOMETRY_FILTER_NAME, GRID_AUTO_FILL_DEFAULTS, GRID_CELL_ATTR, GRID_CELL_ID_PREFIX, GRID_DRAGGING_ATTR, GRID_DRAG_SOURCE_ATTR, GRID_DROP_TARGET_ATTR, GRID_FILL_DEFAULTS, GRID_HANDLE_ATTR, GRID_HANDLE_PROPS, GRID_ROW_ID_PREFIX, GlobalContext, GlobalProvider, GridRowContainer, HANDLE_BLEED_PX, Header, HeaderContainer, HeaderFontColorMixin, HeaderFrontView, HeaderTemplate, HeaderTitleContainer, HiddenTitleItems, IconContainer, ImageContainer, LEFT_PANEL_HEADER_HEIGHT, Layer, LayerDescription, LayerGroupList, LayerIcon, LayerIconContainer, LayerListContainer, LayerTree, LayersContainer, LayersListWrapper, LinearProgressContainer, LogTerminal, LogoContainer, MAP_VIEW_FILTER_NAMES, MAX_CHART_WIDTH, MAX_TRACKS, MIN_TRACK_PX, MIN_TRACK_RATIO, Map$1 as Map, MapContext, MapProvider, NON_TRACK_SLOT_IDS, NO_CELL_DRAG_SELECTOR, NO_CONTENT_VALUE, NUMERIC_ATTRIBUTE_TYPES, NoLiveSnapshotContainer, OBJECT_FITS, OVERFLOWS, OneColumnContainer, PIE_CHART_TOOLTIP_STYLE, POLL_SUBTASK_INTERVAL_MS, POLL_SUBTASK_TIMEOUT_MS, PROVIDER_PREFIX, PageNavigator, PageTitle, PageTitleContainer, PagesContainer, Pagination, PresentationHeader, PresentationHeaderButtons, PresentationHeaderTools, PresentationPanelContainer, PresentationPanelWrapper, PresentationWrapper, ProgressContainer, ProviderPrefix, QUERY_DESCRIPTION_CACHE_TTL, RoundedBackgroundContainer, SAVE_HOOK_RESULT_DURATION, SERVER_NOTIFICATION_EVENT, STACK_BAR_TOTAL_HEIGHT, ScalingFactor, ServerNotificationsContext, ServerNotificationsProvider, SlideshowContainer, SmallPreviewContainer$1 as SmallPreviewContainer, SmallPreviewControl, SmallPreviewCounter, SmallPreviewImages, SmallPreviewLeft, SmallPreviewRight, StackBar, StructuredDataContainer, SvgImage, TILE_ALIGNMENTS, TIME_ZONE_FORMAT, TITLE_SLOT_IDS, TabsContainer, TextTrim, ThemeName, TitleContainer, TopContainer, TopContainerButtons, TwoColumnContainer, UploadContainer, VIEW_MODES, WidgetType, ZOOM_FILTER_NAME, addDataSource, addDataSources, adjustColor, applyFiltersToCondition, applyQueryFilters, applyTreeFilterToCondition, applyVarsToCondition, asAttributeName, asChartId, asContainerId, asDataSourceName, asFilterName, asLayerName, asModalId, asResourceId, asTabId, buildGridTemplate, buildTrackTemplate, checkEqualOrIncludes, checkIsLoading, collectConfigIds, containsNodeId, createConfigLayer, createConfigPage, createGridCell, createGridIdFactory, createGridRow, createNewPageId, createSaveNotificationId, dateOptions, debounce, decimalOpacityToHex, enrichStyleItemsWithIds, enrichStyleModelsWithIds, eqlParametersToPayload, fetchQueryDescription, findAttributeInExpression, findCellContext, formatArea, formatAttributeValue, formatChartRelatedValue, formatConditionValue, formatDataSourceCondition, formatDate$1 as formatDate, formatElementValue, formatLength, formatNumber, formatPolygonMeasure, geometryToEwkt, getActualExtrusionHeight, getAttributeByName, getAttributeConfigurationByName, getAttributeIconElement, getAttributeIconUrl, getAttributeValue, getAttributesConfiguration, getAverageTrackSize, getChartAxes, getChartFilterName, getChartMarkers, getConfigFilter, getContainerComponent, getDashboardHeader, getDataFromAttributes, getDataFromRelatedFeatures, getDataSource, getDataSourceFilterValue, getDataSourceLayerInfo, getDate, getDefaultConfig, getDisplayTemplateNameFromAttribute, getElementValue, getFeatureAttributes, getFeatureCardHeader, getFilterComponent, getFilterSelectedItems, getFilterValue, getFormattedAttributes, getGradientColors, getImageUrl, getLayerClientStyle, getLayerInfo, getLayerInfoAttribute, getLayerInfoFromDataSources, getLayoutChildren, getMapViewDataSources, getPagesFromConfig, getPagesFromProjectInfo, getProxyService, getRelatedAttribute, getRenderElement, getResourceUrl, getRootElementId, getSelectedFilterValue, getSlideshowImages, getStyleAttributes, getSvgUrl, getTemplateNameFromAttribute, getThemeByName, getTotalFromAttributes, getTotalFromRelatedFeatures, getTrackSize, getTrackSizeKey, getTrackSizes, getWrapperSizeStyle, hasContainerBgImage, hexToRgba, isCrossOriginUrl, isEmptyElementValue, isEmptyValue, isFeaturesFilterValue, isFillSize, isFrSize, isGridNode, isHiddenEmptyValue, isHookActive, isLayerService, isNotValidSelectedTab, isNumeric, isObject, isProxyService, isRootOwningContainer, isTreeFilterValue, isVisibleContainer, mapNodeById, mergeAttributeConfigurations, metersPerPixel, noMarginMixin, numberOptions, parseFrValue, parseIconNames, parseIconNamesFromClientStyle, pieChartTooltipFromAttributes, pieChartTooltipFromRelatedFeatures, pointOptions, removeDataSource, removeTracks, replaceNodesByIds, rgbToHex, roundFr, roundTotalSum, setLayoutChildren, sizeCssMixin, sliceShownOtherItems, stretchPalette, timeOptions, toConditionsArray, toCssSize, toFrSize, toPxNumber, toRenderableValue, tooltipNameFromAttributes, tooltipValueFromAttributes, tooltipValueFromRelatedFeatures, transparentizeColor, updateDataSource, useAfterSave, useAppHeight, useAttachmentDownload, useAttachmentItems, useAttachmentPreviewImages, useAutoCompleteControl, useBeforeSave, useBgImageHost, useChartChange, useChartData, useContainerAttributes, useContainerRoot, useCurrentPageLayers, useCustomFeatureSelect, useDashboardHeader, useDataSourceLoading, useDataSources, useDebouncedCallback, useDiffPage, useEditGroupAttributes, useEqualTileWidth, useExpandableContainers, useExportPdf, useFeatureSaveHooks, useFetchImageWithAuth, useFetchWithAuth, useGetConfigLayer, useGlobalContext, useHeaderRender, useHideIfEmptyDataSource, useIconsFromLayers, useLayerHiddenAttributes, useLayerParams, useMapContext, useMapDraw, useMapImages, useMaxZoomTo, useProjectDashboardInit, usePythonSandbox, usePythonTask, useRedrawLayer, useRelatedDataSourceAttributes, useRemoteTask, useRenderElement, useResizeBox, useSavePrototypeBuilder, useServerNotificationsContext, useShownOtherItems, useToggle, useUpdateDataSource, useVisibleProjectItems, useWidgetConfig, useWidgetContext, useWidgetFilters, useWidgetPage, useWindowResize, useWrapperSize, useZoomToFeatures, useZoomToPoint, withTrackSize };
19254
+ export { ALIGNMENTS, ALIGN_ITEMS, ATTRIBUTE_ICON_ELEMENT_TYPES, AddButtonRow, AddFeatureButton, AddFeatureContainer, AlertIconContainer, AttachmentContainer, AttributeGalleryContainer, AttributeLabel, BASE_CONTAINER_STYLE, BG_IMAGE_SLOT_ID, BaseMapTheme, CHART_TYPES, COMPACT_FRACTION_DIGITS, CONFIG_PAGES_ID, CONFIG_PAGE_ID, CONTAINERS_GROUP_DEFAULTS, CONTAINER_BODY_ATTRIBUTE, CONTAINER_BODY_FILL_STYLE, CameraContainer, Chart, ChartContainer, ChartLegend, ChartLoading, Container, ContainerBackground, ContainerChildren, ContainerLoading, ContainerRoot, ContainerTemplate, ContainerWrapper, ContainersGroupContainer, DASHBOARD_OVERLAY_Z_INDEX, DEFAULT_ATTRIBUTE_NAME, DEFAULT_BARCHART_RADIUS, DEFAULT_BASE_MAP, DEFAULT_BLUR, DEFAULT_CHART_ANGLE, DEFAULT_CHART_HEIGHT, DEFAULT_CHART_WIDTH, DEFAULT_CIRCLE_RADIUS, DEFAULT_CIRCLE_STROKE_WIDTH, DEFAULT_CIRCLE_STYLE, DEFAULT_COLOR, DEFAULT_DASHBOARD_CONFIG, DEFAULT_DATA_SOURCE_LIMIT, DEFAULT_DROPDOWN_WIDTH, DEFAULT_FILL_EXTRUSION_BASE, DEFAULT_FILL_EXTRUSION_HEIGHT, DEFAULT_FILL_EXTRUSION_STYLE, DEFAULT_FILL_EXTRUSION_VERTICAL_GRADIENT, DEFAULT_FILL_STYLE, DEFAULT_FILTER_PADDING, DEFAULT_GRID_GAP, DEFAULT_HEATMAP_COLOR, DEFAULT_HEATMAP_INTENSITY, DEFAULT_HEATMAP_RADIUS, DEFAULT_HEATMAP_STYLE, DEFAULT_HEATMAP_WEIGHT, DEFAULT_ICON_ANCHOR, DEFAULT_ICON_OVERLAP, DEFAULT_ICON_PADDING, DEFAULT_ICON_ROTATE, DEFAULT_ICON_SIZE, DEFAULT_ID_ATTRIBUTE_NAME, DEFAULT_LAT, DEFAULT_LINE_CAP, DEFAULT_LINE_JOIN, DEFAULT_LINE_STYLE, DEFAULT_LINE_WIDTH, DEFAULT_LNG, DEFAULT_OPACITY, DEFAULT_PAGES_CONFIG, DEFAULT_PIECHART_RADIUS, DEFAULT_SYMBOL_COLOR, DEFAULT_SYMBOL_HALO_COLOR, DEFAULT_SYMBOL_PLACEMENT, DEFAULT_SYMBOL_SPACING, DEFAULT_SYMBOL_STYLE, DEFAULT_TEXT_ANCHOR, DEFAULT_TEXT_JUSTIFY, DEFAULT_TEXT_SIZE, DEFAULT_TEXT_TRANSFORM, DEFAULT_TRACK_FR, DEFAULT_TRANSLATE, DEFAULT_ZOOM, DRAG_THRESHOLD_PX, Dashboard, DashboardCheckbox, DashboardChip, DashboardContent, DashboardContext, DashboardDefaultHeader, DashboardHeader, DashboardLoading, DashboardPlaceholder, DashboardPlaceholderWrap, DashboardProvider, DashboardWrapper, DataSourceContainer, DataSourceError, DataSourceErrorContainer, DataSourceInnerContainer, DataSourceProgressContainer, DateFormat, DefaultAttributesContainer, DefaultHeaderContainer, DefaultHeaderWrapper, DividerContainer, EMPTY_DATA_SOURCE_LAYER_INFO, EXTENT_FILTER_NAME, EditGeometryType, ElementButton, ElementCamera, ElementChart, ElementChips, ElementControl, ElementIcon, ElementImage, ElementLegend, ElementLink, ElementMarkdown, ElementSlideshow, ElementSvg, ElementTable, ElementTooltip, ElementValueWrapper, ExpandableTitle, FEATURE_CARD_DEFAULT_COLORS, FEATURE_CARD_OTHER_COLOR, FILL_SIZE, FILTERED_VALUE_OPACITY, FILTER_PREFIX, FR_PRECISION, FeatureCardBackgroundHeader, FeatureCardButtons, FeatureCardContext, FeatureCardDefaultHeader, FeatureCardHeader, FeatureCardProvider, FeatureCardSlideshowHeader, FeatureCardTitle, FeatureControls, FeatureTitleContainer, FiltersContainer, GEOMETRY_ATTRIBUTE, GEOMETRY_FILTER_NAME, GRID_AUTO_FILL_DEFAULTS, GRID_CELL_ATTR, GRID_CELL_ID_PREFIX, GRID_DRAGGING_ATTR, GRID_DRAG_SOURCE_ATTR, GRID_DROP_TARGET_ATTR, GRID_FILL_DEFAULTS, GRID_HANDLE_ATTR, GRID_HANDLE_PROPS, GRID_ROW_ID_PREFIX, GlobalContext, GlobalProvider, GridRowContainer, HANDLE_BLEED_PX, Header, HeaderContainer, HeaderFontColorMixin, HeaderFrontView, HeaderTemplate, HeaderTitleContainer, HiddenTitleItems, IconContainer, ImageContainer, LEFT_PANEL_HEADER_HEIGHT, Layer, LayerDescription, LayerGroupList, LayerIcon, LayerIconContainer, LayerListContainer, LayerTree, LayersContainer, LayersListWrapper, LinearProgressContainer, LogTerminal, LogoContainer, MAP_VIEW_FILTER_NAMES, MAX_CHART_WIDTH, MAX_TRACKS, MIN_TRACK_PX, MIN_TRACK_RATIO, Map$1 as Map, MapContext, MapProvider, NON_TRACK_SLOT_IDS, NO_CELL_DRAG_SELECTOR, NO_CONTENT_VALUE, NUMERIC_ATTRIBUTE_TYPES, NoLiveSnapshotContainer, OBJECT_FITS, OVERFLOWS, OneColumnContainer, PIE_CHART_TOOLTIP_STYLE, POLL_SUBTASK_INTERVAL_MS, POLL_SUBTASK_TIMEOUT_MS, PROVIDER_PREFIX, PageNavigator, PageTitle, PageTitleContainer, PagesContainer, Pagination, PresentationHeader, PresentationHeaderButtons, PresentationHeaderTools, PresentationPanelContainer, PresentationPanelWrapper, PresentationWrapper, ProgressContainer, ProviderPrefix, QUERY_DESCRIPTION_CACHE_TTL, RoundedBackgroundContainer, SAVE_HOOK_RESULT_DURATION, SERVER_NOTIFICATION_EVENT, STACK_BAR_TOTAL_HEIGHT, ScalingFactor, ServerNotificationsContext, ServerNotificationsProvider, SlideshowContainer, SmallPreviewContainer$1 as SmallPreviewContainer, SmallPreviewControl, SmallPreviewCounter, SmallPreviewImages, SmallPreviewLeft, SmallPreviewRight, StackBar, StructuredDataContainer, SvgImage, TILE_ALIGNMENTS, TIME_ZONE_FORMAT, TITLE_SLOT_IDS, TabsContainer, TextTrim, ThemeName, TitleContainer, TopContainer, TopContainerButtons, TwoColumnContainer, UploadContainer, VIEW_MODES, VoteContainer, WidgetType, ZOOM_FILTER_NAME, addDataSource, addDataSources, adjustColor, applyFiltersToCondition, applyQueryFilters, applyTreeFilterToCondition, applyVarsToCondition, asAttributeName, asChartId, asContainerId, asDataSourceName, asFilterName, asLayerName, asModalId, asResourceId, asTabId, buildGridTemplate, buildTrackTemplate, checkEqualOrIncludes, checkIsLoading, collectConfigIds, containsNodeId, createConfigLayer, createConfigPage, createGridCell, createGridIdFactory, createGridRow, createNewPageId, createSaveNotificationId, dateOptions, debounce, decimalOpacityToHex, enrichStyleItemsWithIds, enrichStyleModelsWithIds, eqlParametersToPayload, fetchQueryDescription, findAttributeInExpression, findCellContext, formatArea, formatAttributeValue, formatChartRelatedValue, formatConditionValue, formatDataSourceCondition, formatDate$1 as formatDate, formatElementValue, formatLength, formatNumber, formatPolygonMeasure, geometryToEwkt, getActualExtrusionHeight, getAttributeByName, getAttributeConfigurationByName, getAttributeIconElement, getAttributeIconUrl, getAttributeValue, getAttributesConfiguration, getAverageTrackSize, getChartAxes, getChartFilterName, getChartMarkers, getConfigFilter, getContainerComponent, getDashboardHeader, getDataFromAttributes, getDataFromRelatedFeatures, getDataSource, getDataSourceFilterValue, getDataSourceLayerInfo, getDate, getDefaultConfig, getDisplayTemplateNameFromAttribute, getElementValue, getFeatureAttributes, getFeatureCardHeader, getFilterComponent, getFilterSelectedItems, getFilterValue, getFormattedAttributes, getGradientColors, getImageUrl, getLayerClientStyle, getLayerInfo, getLayerInfoAttribute, getLayerInfoFromDataSources, getLayoutChildren, getMapViewDataSources, getPagesFromConfig, getPagesFromProjectInfo, getProxyService, getRelatedAttribute, getRenderElement, getResourceUrl, getRootElementId, getSelectedFilterValue, getSlideshowImages, getStyleAttributes, getSvgUrl, getTemplateNameFromAttribute, getThemeByName, getTotalFromAttributes, getTotalFromRelatedFeatures, getTrackSize, getTrackSizeKey, getTrackSizes, getWrapperSizeStyle, hasContainerBgImage, hexToRgba, isCrossOriginUrl, isEmptyElementValue, isEmptyValue, isFeaturesFilterValue, isFillSize, isFrSize, isGridNode, isHiddenEmptyValue, isHookActive, isLayerService, isNotValidSelectedTab, isNumeric, isObject, isProxyService, isRootOwningContainer, isTreeFilterValue, isVisibleContainer, mapNodeById, mergeAttributeConfigurations, metersPerPixel, noMarginMixin, numberOptions, parseFrValue, parseIconNames, parseIconNamesFromClientStyle, pieChartTooltipFromAttributes, pieChartTooltipFromRelatedFeatures, pointOptions, removeDataSource, removeTracks, replaceNodesByIds, rgbToHex, roundFr, roundTotalSum, setLayoutChildren, sizeCssMixin, sliceShownOtherItems, stretchPalette, timeOptions, toConditionsArray, toCssSize, toFrSize, toPxNumber, toRenderableValue, tooltipNameFromAttributes, tooltipValueFromAttributes, tooltipValueFromRelatedFeatures, transparentizeColor, updateDataSource, useAfterSave, useAppHeight, useAttachmentDownload, useAttachmentItems, useAttachmentPreviewImages, useAutoCompleteControl, useBeforeSave, useBgImageHost, useChartChange, useChartData, useContainerAttributes, useContainerRoot, useCurrentPageLayers, useCustomFeatureSelect, useDashboardHeader, useDataSourceLoading, useDataSources, useDebouncedCallback, useDiffPage, useEditGroupAttributes, useEqualTileWidth, useExpandableContainers, useExportPdf, useFeatureSaveHooks, useFetchImageWithAuth, useFetchWithAuth, useGetConfigLayer, useGlobalContext, useHeaderRender, useHideIfEmptyDataSource, useIconsFromLayers, useLayerHiddenAttributes, useLayerParams, useMapContext, useMapDraw, useMapImages, useMaxZoomTo, useProjectDashboardInit, usePythonSandbox, usePythonTask, useRedrawLayer, useRelatedDataSourceAttributes, useRemoteTask, useRenderElement, useResizeBox, useSavePrototypeBuilder, useServerNotificationsContext, useShownOtherItems, useToggle, useUpdateDataSource, useVisibleProjectItems, useWidgetConfig, useWidgetContext, useWidgetFilters, useWidgetPage, useWindowResize, useWrapperSize, useZoomToFeatures, useZoomToPoint, withTrackSize };
18524
19255
  //# sourceMappingURL=react.esm.js.map