@evergis/react 4.0.138 → 4.0.140

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.
@@ -23,7 +23,8 @@ export declare const VoteVariantHead: import('styled-components').StyledComponen
23
23
  export declare const VoteVariantText: import('styled-components').StyledComponent<"span", any, {}, never>;
24
24
  export declare const VoteVariantCount: import('styled-components').StyledComponent<"span", any, {}, never>;
25
25
  export declare const VoteAcceptedBadge: import('styled-components').StyledComponent<"div", any, import('@evergis/uilib-gl').FlexProps, never>;
26
- export declare const VoteAuthHint: import('styled-components').StyledComponent<"div", any, {}, never>;
26
+ /** Мелкая серая подпись под контролами: почему нельзя голосовать или почему кнопка неактивна. */
27
+ export declare const VoteHint: import('styled-components').StyledComponent<"div", any, {}, never>;
27
28
  export declare const VoteEditLink: import('styled-components').StyledComponent<"button", any, {}, never>;
28
29
  export declare const VoteShareBlock: import('styled-components').StyledComponent<"div", any, import('@evergis/uilib-gl').FlexProps, never>;
29
30
  export declare const VoteShareChips: import('styled-components').StyledComponent<"div", any, import('@evergis/uilib-gl').FlexProps, never>;
@@ -1,7 +1,6 @@
1
1
  export * from './useMapDraw';
2
2
  export * from './useMapContext';
3
3
  export * from './useMapImages';
4
- export * from './useIconsFromLayers';
5
4
  export * from './useRedrawLayer';
6
5
  export * from './useZoomToFeatures';
7
6
  export * from './useZoomToPoint';
package/dist/index.js CHANGED
@@ -6355,300 +6355,23 @@ const useMapImages = ({ images }) => {
6355
6355
  };
6356
6356
  void loadAllImages();
6357
6357
  }, [mapLoaded, images, map, addImage]);
6358
- return { loaded, errors, addImage, removeImage, hasImage };
6359
- };
6360
-
6361
- const enrichStyleItemsWithIds = (style) => {
6362
- return {
6363
- ...style,
6364
- items: style.items.map(item => {
6365
- return {
6366
- ...item,
6367
- id: item.id || uuid.v4(),
6368
- };
6369
- }),
6370
- };
6371
- };
6372
-
6373
- const enrichStyleModelsWithIds = (style) => {
6374
- return {
6375
- ...style,
6376
- models: style.models.map(model => {
6377
- return {
6378
- ...model,
6379
- id: model.id || uuid.v4(),
6380
- };
6381
- }),
6382
- };
6383
- };
6384
-
6385
- const findAttributeInExpression = (expression) => {
6386
- if (Array.isArray(expression) && expression.length === 2 && expression[0] === "get") {
6387
- return [expression[1]];
6388
- }
6389
- return expression.reduce((acc, curr) => {
6390
- if (!Array.isArray(curr)) {
6391
- return acc;
6392
- }
6393
- if (curr[0] === "get") {
6394
- return [...new Set([...acc, curr[1]])];
6395
- }
6396
- return [...new Set([...acc, ...findAttributeInExpression(curr)])];
6397
- }, []);
6398
- };
6399
-
6400
- const getActualExtrusionHeight = (paint) => {
6401
- return Array.isArray(paint?.["fill-extrusion-height"]) && paint?.["fill-extrusion-height"][0] === "+"
6402
- ? paint?.["fill-extrusion-height"][1]
6403
- : paint?.["fill-extrusion-height"];
6404
- };
6405
-
6406
- const getLayerClientStyle = (layer) => {
6407
- const clientStyle = layer?.configuration?.clientStyle;
6408
- return {
6409
- ...clientStyle,
6410
- items: clientStyle?.items ?? [],
6411
- };
6412
- };
6413
-
6414
- const extractAttributesFromObject = (obj) => {
6415
- if (!obj || lodash.isEmpty(obj)) {
6416
- return [];
6417
- }
6418
- return Object.entries(obj).reduce((acc, [, value]) => {
6419
- if (!Array.isArray(value)) {
6420
- return acc;
6358
+ React.useEffect(() => {
6359
+ if (!mapLoaded) {
6360
+ return;
6421
6361
  }
6422
- return [...new Set([...acc, ...findAttributeInExpression(value)])];
6423
- }, []);
6424
- };
6425
- const getStyleAttributes = (style) => {
6426
- if (!style) {
6427
- return [];
6428
- }
6429
- return style?.items?.reduce((acc, curr) => {
6430
- const paintAttributes = extractAttributesFromObject(curr.paint);
6431
- const layoutAttributes = extractAttributesFromObject(curr.layout);
6432
- const filterAttributes = curr.filter
6433
- ? findAttributeInExpression(curr.filter)
6434
- : [];
6435
- return [...new Set([...acc, ...paintAttributes, ...layoutAttributes, ...filterAttributes])];
6436
- }, []);
6437
- };
6438
-
6439
- /**
6440
- * Извлекает имена иконок из выражения icon-image.
6441
- *
6442
- * Поддерживает:
6443
- * - Строки: "parking" → ["parking"]
6444
- * - match: ["match", ["get", "type"], "a", "icon-a", "b", "icon-b", "default"] → ["icon-a", "icon-b", "default"]
6445
- * - case: ["case", [...], "icon-a", "icon-b"] → ["icon-a", "icon-b"]
6446
- * - coalesce: ["coalesce", ["image", "icon-a"], ["image", "icon-b"]] → ["icon-a", "icon-b"]
6447
- * - image: ["image", "icon-a"] → ["icon-a"]
6448
- *
6449
- * @param iconImage - значение icon-image из layout
6450
- * @returns массив имён иконок
6451
- */
6452
- const parseIconNames = (iconImage) => {
6453
- if (!iconImage) {
6454
- return [];
6455
- }
6456
- // Простая строка: "parking"
6457
- if (typeof iconImage === "string") {
6458
- return [iconImage];
6459
- }
6460
- // Выражение Mapbox
6461
- if (Array.isArray(iconImage) && iconImage.length > 0) {
6462
- const [operator, ...args] = iconImage;
6463
- switch (operator) {
6464
- // ["match", input, label1, output1, label2, output2, ..., fallback]
6465
- case "match": {
6466
- const results = [];
6467
- // Пропускаем первый аргумент (input), берём каждый второй начиная с индекса 2
6468
- for (let i = 2; i < args.length; i += 2) {
6469
- if (typeof args[i] === "string") {
6470
- results.push(args[i]);
6471
- }
6472
- else if (Array.isArray(args[i])) {
6473
- results.push(...parseIconNames(args[i]));
6474
- }
6475
- }
6476
- // Последний аргумент - fallback (если нечётное количество после input)
6477
- const lastArg = args[args.length - 1];
6478
- if (typeof lastArg === "string") {
6479
- results.push(lastArg);
6480
- }
6481
- else if (Array.isArray(lastArg)) {
6482
- results.push(...parseIconNames(lastArg));
6483
- }
6484
- return [...new Set(results)];
6485
- }
6486
- // ["case", condition1, output1, condition2, output2, ..., fallback]
6487
- case "case": {
6488
- const results = [];
6489
- for (let i = 1; i < args.length; i += 2) {
6490
- if (typeof args[i] === "string") {
6491
- results.push(args[i]);
6492
- }
6493
- else if (Array.isArray(args[i])) {
6494
- results.push(...parseIconNames(args[i]));
6495
- }
6362
+ map.current.on("styleimagemissing", async (e) => {
6363
+ const missingImage = images.find(item => item.name === e.id);
6364
+ if (missingImage && !map.current.hasImage(e.id)) {
6365
+ try {
6366
+ await addImage(missingImage);
6496
6367
  }
6497
- // Fallback
6498
- if (args.length % 2 === 0) {
6499
- const lastArg = args[args.length - 1];
6500
- if (typeof lastArg === "string") {
6501
- results.push(lastArg);
6502
- }
6503
- else if (Array.isArray(lastArg)) {
6504
- results.push(...parseIconNames(lastArg));
6505
- }
6368
+ catch {
6369
+ // Ошибка уже записана в errors
6506
6370
  }
6507
- return [...new Set(results)];
6508
- }
6509
- // ["coalesce", expr1, expr2, ...]
6510
- case "coalesce": {
6511
- const results = [];
6512
- args.forEach(arg => {
6513
- if (typeof arg === "string") {
6514
- results.push(arg);
6515
- }
6516
- else if (Array.isArray(arg)) {
6517
- results.push(...parseIconNames(arg));
6518
- }
6519
- });
6520
- return [...new Set(results)];
6521
- }
6522
- // ["image", name] или ["image", name1, name2, ...]
6523
- case "image": {
6524
- return args.filter((arg) => typeof arg === "string");
6525
- }
6526
- // ["concat", ...] или другие - рекурсивно ищем строки
6527
- default: {
6528
- const results = [];
6529
- args.forEach(arg => {
6530
- if (Array.isArray(arg)) {
6531
- results.push(...parseIconNames(arg));
6532
- }
6533
- });
6534
- return [...new Set(results)];
6535
- }
6536
- }
6537
- }
6538
- return [];
6539
- };
6540
- /**
6541
- * Свойства, содержащие имена изображений (иконки и паттерны).
6542
- */
6543
- const IMAGE_PROPERTIES = {
6544
- layout: ["icon-image"],
6545
- paint: ["fill-pattern", "line-pattern", "fill-extrusion-pattern"],
6546
- };
6547
- /**
6548
- * Извлекает все имена изображений из clientStyle.
6549
- * Включает иконки (icon-image) и паттерны (fill-pattern, line-pattern, fill-extrusion-pattern).
6550
- *
6551
- * @param clientStyle - конфигурация стиля слоя
6552
- * @returns массив уникальных имён изображений
6553
- */
6554
- const parseIconNamesFromClientStyle = (clientStyle) => {
6555
- if (!clientStyle?.items) {
6556
- return [];
6557
- }
6558
- const imageNames = new Set();
6559
- clientStyle.items.forEach(item => {
6560
- // Парсим icon-image из layout
6561
- IMAGE_PROPERTIES.layout.forEach(prop => {
6562
- const value = item.layout?.[prop];
6563
- if (value) {
6564
- parseIconNames(value).forEach(name => imageNames.add(name));
6565
- }
6566
- });
6567
- // Парсим fill-pattern, line-pattern, fill-extrusion-pattern из paint
6568
- IMAGE_PROPERTIES.paint.forEach(prop => {
6569
- const value = item.paint?.[prop];
6570
- if (value) {
6571
- parseIconNames(value).forEach(name => imageNames.add(name));
6572
- }
6573
- });
6574
- });
6575
- return [...imageNames];
6576
- };
6577
-
6578
- /**
6579
- * Находит конфигурацию иконки в clientStyle.icons по имени.
6580
- */
6581
- const findIconConfig = (layers, iconName) => {
6582
- for (const layer of layers) {
6583
- const icon = layer.clientStyle?.icons?.find(i => i.name === iconName);
6584
- if (icon) {
6585
- return icon;
6586
- }
6587
- }
6588
- return undefined;
6589
- };
6590
- /**
6591
- * Хук для автоматической загрузки иконок из clientStyle слоёв.
6592
- *
6593
- * Парсит все icon-image выражения из clientStyle.items и загружает
6594
- * соответствующие иконки из clientStyle.icons или по конвенции baseUrl.
6595
- *
6596
- * @example
6597
- * ```tsx
6598
- * const layers = [
6599
- * {
6600
- * layerId: 'parking',
6601
- * clientStyle: {
6602
- * icons: [
6603
- * { name: 'parking-icon', url: '/icons/parking.svg', sdf: true }
6604
- * ],
6605
- * items: [{
6606
- * type: 'symbol',
6607
- * layout: { 'icon-image': 'parking-icon' }
6608
- * }]
6609
- * }
6610
- * }
6611
- * ];
6612
- *
6613
- * const { loaded, errors } = useIconsFromLayers({ layers });
6614
- *
6615
- * if (!loaded) return <Loading />;
6616
- * ```
6617
- */
6618
- const useIconsFromLayers = ({ layers, baseUrl = "/icons", defaultExtension = "svg", }) => {
6619
- // Собираем все уникальные имена иконок из всех слоёв
6620
- const requestedIcons = React.useMemo(() => {
6621
- const iconNames = new Set();
6622
- layers.forEach(layer => {
6623
- const names = parseIconNamesFromClientStyle(layer.clientStyle);
6624
- names.forEach(name => iconNames.add(name));
6625
- });
6626
- return [...iconNames];
6627
- }, [layers]);
6628
- // Формируем конфигурации для загрузки
6629
- const imageConfigs = React.useMemo(() => {
6630
- return requestedIcons.map(name => {
6631
- // Ищем в clientStyle.icons
6632
- const customIcon = findIconConfig(layers, name);
6633
- if (customIcon) {
6634
- return {
6635
- name: customIcon.name,
6636
- url: customIcon.url,
6637
- sdf: customIcon.sdf,
6638
- size: customIcon.size,
6639
- pixelRatio: customIcon.pixelRatio,
6640
- };
6641
6371
  }
6642
- // Fallback: конструируем URL по конвенции
6643
- return {
6644
- name,
6645
- url: `${baseUrl}/${name}.${defaultExtension}`,
6646
- sdf: true,
6647
- };
6648
6372
  });
6649
- }, [requestedIcons, layers, baseUrl, defaultExtension]);
6650
- const { loaded, errors } = useMapImages({ images: imageConfigs });
6651
- return { loaded, errors, requestedIcons };
6373
+ }, [mapLoaded]);
6374
+ return { loaded, errors, addImage, removeImage, hasImage };
6652
6375
  };
6653
6376
 
6654
6377
  const useRedrawLayer = () => {
@@ -12185,7 +11908,8 @@ const VoteAcceptedBadge = styled(uilibGl.Flex).withConfig({ displayName: "VoteAc
12185
11908
  color: #ffffff;
12186
11909
  font-size: 0.875rem;
12187
11910
  `;
12188
- const VoteAuthHint = styled.div.withConfig({ displayName: "VoteAuthHint", componentId: "sc-l6fdf1" }) `
11911
+ /** Мелкая серая подпись под контролами: почему нельзя голосовать или почему кнопка неактивна. */
11912
+ const VoteHint = styled.div.withConfig({ displayName: "VoteHint", componentId: "sc-1ortkdd" }) `
12189
11913
  font-size: 0.8125rem;
12190
11914
  color: ${({ theme: { palette } }) => palette.textSecondary};
12191
11915
  `;
@@ -12287,8 +12011,18 @@ const useVoteForm = (initialValues) => {
12287
12011
  const removeVariant = React.useCallback((index) => {
12288
12012
  setVariants(prev => prev.filter((_, itemIndex) => itemIndex !== index));
12289
12013
  }, []);
12290
- const isValid = React.useMemo(() => Boolean(categoryId && text.trim() && variants.length >= MIN_VARIANTS), [categoryId, text, variants.length]);
12291
- const values = React.useMemo(() => ({ categoryId, text: text.trim(), multiSelect, variants }), [categoryId, text, multiSelect, variants]);
12014
+ /**
12015
+ * Варианты вместе с недобавленным черновиком.
12016
+ *
12017
+ * Последний вариант часто набирают и жмут «Создать», не нажав Enter: чипсом он ещё не стал, но
12018
+ * пользователь его уже ввёл. Считать такой текст вариантом честнее, чем гасить кнопку и молчать.
12019
+ */
12020
+ const allVariants = React.useMemo(() => {
12021
+ const pending = draft.trim();
12022
+ return pending ? [...variants, { text: pending }] : variants;
12023
+ }, [variants, draft]);
12024
+ const isValid = React.useMemo(() => Boolean(categoryId && text.trim() && allVariants.length >= MIN_VARIANTS), [categoryId, text, allVariants.length]);
12025
+ const values = React.useMemo(() => ({ categoryId, text: text.trim(), multiSelect, variants: allVariants }), [categoryId, text, multiSelect, allVariants]);
12292
12026
  return {
12293
12027
  categoryId,
12294
12028
  setCategoryId,
@@ -12318,7 +12052,10 @@ const VoteCreateForm = ({ categories, initialValues, isEditing, canDelete, submi
12318
12052
  }
12319
12053
  } }), jsxRuntime.jsxs(uilibGl.Flex, { alignItems: "center", justifyContent: "space-between", children: [jsxRuntime.jsx(VoteSectionLabel, { children: t("vote.multiSelect", { ns: VOTE_NS, defaultValue: "Множественный выбор" }) }), jsxRuntime.jsx(uilibGl.Switch, { checked: multiSelect, onChange: () => setMultiSelect(prev => !prev) })] }), jsxRuntime.jsx(uilibGl.RaisedButton, { primary: true, disabled: !isValid || submitting, onClick: () => onSubmit(values), children: isEditing
12320
12054
  ? t("vote.save", { ns: VOTE_NS, defaultValue: "Сохранить" })
12321
- : t("vote.create", { ns: VOTE_NS, defaultValue: "Создать голосование" }) }), isEditing && (jsxRuntime.jsxs(uilibGl.Flex, { alignItems: "center", justifyContent: "space-between", children: [onCancel && (jsxRuntime.jsx(VoteEditLink, { onClick: onCancel, children: t("vote.cancel", { ns: VOTE_NS, defaultValue: "Отмена" }) })), canDelete && onDelete && (jsxRuntime.jsx(uilibGl.RaisedButton, { error: true, disabled: submitting, onClick: onDelete, children: t("vote.delete", { ns: VOTE_NS, defaultValue: "Удалить голосование" }) }))] }))] }));
12055
+ : t("vote.create", { ns: VOTE_NS, defaultValue: "Создать голосование" }) }), !isValid && (jsxRuntime.jsx(VoteHint, { children: t("vote.formHint", {
12056
+ ns: VOTE_NS,
12057
+ defaultValue: "Нужны категория, вопрос и минимум два варианта ответа",
12058
+ }) })), isEditing && (jsxRuntime.jsxs(uilibGl.Flex, { alignItems: "center", justifyContent: "space-between", children: [onCancel && (jsxRuntime.jsx(VoteEditLink, { onClick: onCancel, children: t("vote.cancel", { ns: VOTE_NS, defaultValue: "Отмена" }) })), canDelete && onDelete && (jsxRuntime.jsx(uilibGl.RaisedButton, { error: true, disabled: submitting, onClick: onDelete, children: t("vote.delete", { ns: VOTE_NS, defaultValue: "Удалить голосование" }) }))] }))] }));
12322
12059
  };
12323
12060
 
12324
12061
  /** Блок «Поделиться в соцсетях» — пока только вёрстка, без рабочих ссылок. */
@@ -12342,7 +12079,7 @@ const VoteResults = ({ question, category, results, screen, isOwner, hasAnswers,
12342
12079
  }, [question.multiSelect]);
12343
12080
  const canSubmit = React.useMemo(() => selected.length > 0 && !submitting, [selected.length, submitting]);
12344
12081
  return (jsxRuntime.jsxs(VoteWrapper, { children: [category && jsxRuntime.jsx(VoteCategoryLabel, { children: category.name }), jsxRuntime.jsx(VoteQuestionText, { children: question.text }), jsxRuntime.jsx(VoteVariantList, { column: true, value: selected[0] ?? "", children: results.map(({ id, text, count, percent }) => (jsxRuntime.jsxs(VoteVariantRow, { children: [jsxRuntime.jsxs(VoteVariantHead, { children: [showControls &&
12345
- (question.multiSelect ? (jsxRuntime.jsx(uilibGl.Checkbox, { checked: selected.includes(id), onChange: () => toggle(id) })) : (jsxRuntime.jsx(uilibGl.Radio, { value: id, checked: selected.includes(id), onChange: () => toggle(id) }))), jsxRuntime.jsx(VoteVariantText, { children: text }), jsxRuntime.jsx(VoteVariantCount, { children: count })] }), jsxRuntime.jsx(uilibGl.LinearProgress, { done: percent })] }, id))) }), screen === "unauthenticated" && (jsxRuntime.jsx(VoteAuthHint, { children: t("vote.authHint", { ns: VOTE_NS, defaultValue: "Чтобы проголосовать, пожалуйста, авторизуйтесь" }) })), screen === "voted" && (jsxRuntime.jsxs(VoteAcceptedBadge, { children: [jsxRuntime.jsx(uilibGl.Icon, { kind: "success" }), t("vote.accepted", { ns: VOTE_NS, defaultValue: "Ваш ответ принят" })] })), showControls && (jsxRuntime.jsx(uilibGl.RaisedButton, { primary: true, disabled: !canSubmit, onClick: () => onSubmit(selected), children: t("vote.submit", { ns: VOTE_NS, defaultValue: "Отправить ответ" }) })), canEdit && onEdit && (jsxRuntime.jsx(uilibGl.Flex, { children: jsxRuntime.jsx(VoteEditLink, { onClick: onEdit, children: t("vote.edit", { ns: VOTE_NS, defaultValue: "Редактировать" }) }) })), showShare && jsxRuntime.jsx(VoteShare, {})] }));
12082
+ (question.multiSelect ? (jsxRuntime.jsx(uilibGl.Checkbox, { checked: selected.includes(id), onChange: () => toggle(id) })) : (jsxRuntime.jsx(uilibGl.Radio, { value: id, checked: selected.includes(id), onChange: () => toggle(id) }))), jsxRuntime.jsx(VoteVariantText, { children: text }), jsxRuntime.jsx(VoteVariantCount, { children: count })] }), jsxRuntime.jsx(uilibGl.LinearProgress, { done: percent })] }, id))) }), screen === "unauthenticated" && (jsxRuntime.jsx(VoteHint, { children: t("vote.authHint", { ns: VOTE_NS, defaultValue: "Чтобы проголосовать, пожалуйста, авторизуйтесь" }) })), screen === "voted" && (jsxRuntime.jsxs(VoteAcceptedBadge, { children: [jsxRuntime.jsx(uilibGl.Icon, { kind: "success" }), t("vote.accepted", { ns: VOTE_NS, defaultValue: "Ваш ответ принят" })] })), showControls && (jsxRuntime.jsx(uilibGl.RaisedButton, { primary: true, disabled: !canSubmit, onClick: () => onSubmit(selected), children: t("vote.submit", { ns: VOTE_NS, defaultValue: "Отправить ответ" }) })), canEdit && onEdit && (jsxRuntime.jsx(uilibGl.Flex, { children: jsxRuntime.jsx(VoteEditLink, { onClick: onEdit, children: t("vote.edit", { ns: VOTE_NS, defaultValue: "Редактировать" }) }) })), showShare && jsxRuntime.jsx(VoteShare, {})] }));
12346
12083
  };
12347
12084
 
12348
12085
  /**
@@ -19021,6 +18758,223 @@ const RasterLayer = ({ layer, tileUrl, visible, beforeId, }) => {
19021
18758
  return (jsxRuntime.jsx(MapGL.Source, { id: layer.name, type: "raster", tiles: tiles, children: jsxRuntime.jsx(MapGL.Layer, { id: layer.name, type: "raster", "source-layer": "default", beforeId: beforeId, layout: { visibility: visible ? "visible" : "none" } }) }, `${layer.name}-${tileUrl}`));
19022
18759
  };
19023
18760
 
18761
+ const enrichStyleItemsWithIds = (style) => {
18762
+ return {
18763
+ ...style,
18764
+ items: style.items.map(item => {
18765
+ return {
18766
+ ...item,
18767
+ id: item.id || uuid.v4(),
18768
+ };
18769
+ }),
18770
+ };
18771
+ };
18772
+
18773
+ const enrichStyleModelsWithIds = (style) => {
18774
+ return {
18775
+ ...style,
18776
+ models: style.models.map(model => {
18777
+ return {
18778
+ ...model,
18779
+ id: model.id || uuid.v4(),
18780
+ };
18781
+ }),
18782
+ };
18783
+ };
18784
+
18785
+ const findAttributeInExpression = (expression) => {
18786
+ if (Array.isArray(expression) && expression.length === 2 && expression[0] === "get") {
18787
+ return [expression[1]];
18788
+ }
18789
+ return expression.reduce((acc, curr) => {
18790
+ if (!Array.isArray(curr)) {
18791
+ return acc;
18792
+ }
18793
+ if (curr[0] === "get") {
18794
+ return [...new Set([...acc, curr[1]])];
18795
+ }
18796
+ return [...new Set([...acc, ...findAttributeInExpression(curr)])];
18797
+ }, []);
18798
+ };
18799
+
18800
+ const getActualExtrusionHeight = (paint) => {
18801
+ return Array.isArray(paint?.["fill-extrusion-height"]) && paint?.["fill-extrusion-height"][0] === "+"
18802
+ ? paint?.["fill-extrusion-height"][1]
18803
+ : paint?.["fill-extrusion-height"];
18804
+ };
18805
+
18806
+ const getLayerClientStyle = (layer) => {
18807
+ const clientStyle = layer?.configuration?.clientStyle;
18808
+ return {
18809
+ ...clientStyle,
18810
+ items: clientStyle?.items ?? [],
18811
+ };
18812
+ };
18813
+
18814
+ const extractAttributesFromObject = (obj) => {
18815
+ if (!obj || lodash.isEmpty(obj)) {
18816
+ return [];
18817
+ }
18818
+ return Object.entries(obj).reduce((acc, [, value]) => {
18819
+ if (!Array.isArray(value)) {
18820
+ return acc;
18821
+ }
18822
+ return [...new Set([...acc, ...findAttributeInExpression(value)])];
18823
+ }, []);
18824
+ };
18825
+ const getStyleAttributes = (style) => {
18826
+ if (!style) {
18827
+ return [];
18828
+ }
18829
+ return style?.items?.reduce((acc, curr) => {
18830
+ const paintAttributes = extractAttributesFromObject(curr.paint);
18831
+ const layoutAttributes = extractAttributesFromObject(curr.layout);
18832
+ const filterAttributes = curr.filter
18833
+ ? findAttributeInExpression(curr.filter)
18834
+ : [];
18835
+ return [...new Set([...acc, ...paintAttributes, ...layoutAttributes, ...filterAttributes])];
18836
+ }, []);
18837
+ };
18838
+
18839
+ /**
18840
+ * Извлекает имена иконок из выражения icon-image.
18841
+ *
18842
+ * Поддерживает:
18843
+ * - Строки: "parking" → ["parking"]
18844
+ * - match: ["match", ["get", "type"], "a", "icon-a", "b", "icon-b", "default"] → ["icon-a", "icon-b", "default"]
18845
+ * - case: ["case", [...], "icon-a", "icon-b"] → ["icon-a", "icon-b"]
18846
+ * - coalesce: ["coalesce", ["image", "icon-a"], ["image", "icon-b"]] → ["icon-a", "icon-b"]
18847
+ * - image: ["image", "icon-a"] → ["icon-a"]
18848
+ *
18849
+ * @param iconImage - значение icon-image из layout
18850
+ * @returns массив имён иконок
18851
+ */
18852
+ const parseIconNames = (iconImage) => {
18853
+ if (!iconImage) {
18854
+ return [];
18855
+ }
18856
+ // Простая строка: "parking"
18857
+ if (typeof iconImage === "string") {
18858
+ return [iconImage];
18859
+ }
18860
+ // Выражение Mapbox
18861
+ if (Array.isArray(iconImage) && iconImage.length > 0) {
18862
+ const [operator, ...args] = iconImage;
18863
+ switch (operator) {
18864
+ // ["match", input, label1, output1, label2, output2, ..., fallback]
18865
+ case "match": {
18866
+ const results = [];
18867
+ // Пропускаем первый аргумент (input), берём каждый второй начиная с индекса 2
18868
+ for (let i = 2; i < args.length; i += 2) {
18869
+ if (typeof args[i] === "string") {
18870
+ results.push(args[i]);
18871
+ }
18872
+ else if (Array.isArray(args[i])) {
18873
+ results.push(...parseIconNames(args[i]));
18874
+ }
18875
+ }
18876
+ // Последний аргумент - fallback (если нечётное количество после input)
18877
+ const lastArg = args[args.length - 1];
18878
+ if (typeof lastArg === "string") {
18879
+ results.push(lastArg);
18880
+ }
18881
+ else if (Array.isArray(lastArg)) {
18882
+ results.push(...parseIconNames(lastArg));
18883
+ }
18884
+ return [...new Set(results)];
18885
+ }
18886
+ // ["case", condition1, output1, condition2, output2, ..., fallback]
18887
+ case "case": {
18888
+ const results = [];
18889
+ for (let i = 1; i < args.length; i += 2) {
18890
+ if (typeof args[i] === "string") {
18891
+ results.push(args[i]);
18892
+ }
18893
+ else if (Array.isArray(args[i])) {
18894
+ results.push(...parseIconNames(args[i]));
18895
+ }
18896
+ }
18897
+ // Fallback
18898
+ if (args.length % 2 === 0) {
18899
+ const lastArg = args[args.length - 1];
18900
+ if (typeof lastArg === "string") {
18901
+ results.push(lastArg);
18902
+ }
18903
+ else if (Array.isArray(lastArg)) {
18904
+ results.push(...parseIconNames(lastArg));
18905
+ }
18906
+ }
18907
+ return [...new Set(results)];
18908
+ }
18909
+ // ["coalesce", expr1, expr2, ...]
18910
+ case "coalesce": {
18911
+ const results = [];
18912
+ args.forEach(arg => {
18913
+ if (typeof arg === "string") {
18914
+ results.push(arg);
18915
+ }
18916
+ else if (Array.isArray(arg)) {
18917
+ results.push(...parseIconNames(arg));
18918
+ }
18919
+ });
18920
+ return [...new Set(results)];
18921
+ }
18922
+ // ["image", name] или ["image", name1, name2, ...]
18923
+ case "image": {
18924
+ return args.filter((arg) => typeof arg === "string");
18925
+ }
18926
+ // ["concat", ...] или другие - рекурсивно ищем строки
18927
+ default: {
18928
+ const results = [];
18929
+ args.forEach(arg => {
18930
+ if (Array.isArray(arg)) {
18931
+ results.push(...parseIconNames(arg));
18932
+ }
18933
+ });
18934
+ return [...new Set(results)];
18935
+ }
18936
+ }
18937
+ }
18938
+ return [];
18939
+ };
18940
+ /**
18941
+ * Свойства, содержащие имена изображений (иконки и паттерны).
18942
+ */
18943
+ const IMAGE_PROPERTIES = {
18944
+ layout: ["icon-image"],
18945
+ paint: ["fill-pattern", "line-pattern", "fill-extrusion-pattern"],
18946
+ };
18947
+ /**
18948
+ * Извлекает все имена изображений из clientStyle.
18949
+ * Включает иконки (icon-image) и паттерны (fill-pattern, line-pattern, fill-extrusion-pattern).
18950
+ *
18951
+ * @param clientStyle - конфигурация стиля слоя
18952
+ * @returns массив уникальных имён изображений
18953
+ */
18954
+ const parseIconNamesFromClientStyle = (clientStyle) => {
18955
+ if (!clientStyle?.items) {
18956
+ return [];
18957
+ }
18958
+ const imageNames = new Set();
18959
+ clientStyle.items.forEach(item => {
18960
+ // Парсим icon-image из layout
18961
+ IMAGE_PROPERTIES.layout.forEach(prop => {
18962
+ const value = item.layout?.[prop];
18963
+ if (value) {
18964
+ parseIconNames(value).forEach(name => imageNames.add(name));
18965
+ }
18966
+ });
18967
+ // Парсим fill-pattern, line-pattern, fill-extrusion-pattern из paint
18968
+ IMAGE_PROPERTIES.paint.forEach(prop => {
18969
+ const value = item.paint?.[prop];
18970
+ if (value) {
18971
+ parseIconNames(value).forEach(name => imageNames.add(name));
18972
+ }
18973
+ });
18974
+ });
18975
+ return [...imageNames];
18976
+ };
18977
+
19024
18978
  const VectorLayer = ({ layer, tileUrl, visible, beforeId, getLayerTempStyle, }) => {
19025
18979
  const layerConfiguration = layer?.configuration;
19026
18980
  const clientStyle = layerConfiguration?.clientStyle;
@@ -19680,7 +19634,6 @@ exports.useGetConfigLayer = useGetConfigLayer;
19680
19634
  exports.useGlobalContext = useGlobalContext;
19681
19635
  exports.useHeaderRender = useHeaderRender;
19682
19636
  exports.useHideIfEmptyDataSource = useHideIfEmptyDataSource;
19683
- exports.useIconsFromLayers = useIconsFromLayers;
19684
19637
  exports.useLayerHiddenAttributes = useLayerHiddenAttributes;
19685
19638
  exports.useLayerParams = useLayerParams;
19686
19639
  exports.useMapContext = useMapContext;