Кастомизация карты

Open on CodeSandbox

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/lib/codemirror.css" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/theme/material.css" />
    <script src="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/lib/codemirror.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/mode/javascript/javascript.js"></script>

    <script crossorigin src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

    <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
    <!-- To make the map appear, you must add your apikey -->
    <script src="https://api-maps.yandex.ru/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>

    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
      import type {VectorCustomizationItem, VectorCustomizationElements} from '@yandex/ymaps3-types';
      import {LOCATION, initialCustomization, CustomizationControl, generateColor} from './common';

      window.map = null;
      main();
      async function main() {
          // Waiting for all api elements to be loaded
          await ymaps3.ready;
          const {YMap, YMapDefaultSchemeLayer, YMapControls, YMapControl} = ymaps3;

          let customization = initialCustomization;

          // Create an editor for manual customization of the map
          const editor = CodeMirror.fromTextArea(document.getElementById('code') as HTMLTextAreaElement, {
              mode: 'javascript',
              theme: 'material',
              lineNumbers: true
          });

          // Set the initial size and value to the editor
          editor.setSize('40%', '100%');
          editor.setValue(JSON.stringify(initialCustomization, null, 2));

          // Add an onchange event to the editor that will update the map customization
          const editorChangeHandler = _.debounce(() => {
              customization = JSON.parse(editor.getValue());
              layer.update({customization});
          }, 200);
          editor.on('change', editorChangeHandler);

          // Create a function that we will call when changing the customization of the map through the control
          function updateCustomization() {
              editor.setValue(JSON.stringify(customization, null, 2));
          }

          // Initialize the map
          map = new YMap(
              // Pass the link to the HTMLElement of the container
              document.getElementById('app'),
              // Pass the map initialization parameters
              {location: LOCATION, showScaleInCopyrights: true}
          );

          // Add a map schema layer with a custom customization props
          const layer = new YMapDefaultSchemeLayer({
              customization
          });
          map.addChild(layer);

          // Create a CustomizationControl to change the appearance of the road
          const roadControl = new CustomizationControl({
              title: 'Road',
              changeColorHandler: createChangeColorHandler(['road'], 'geometry'),
              changeOpacityHandler: createChangeOpacityHandler(['road'], 'geometry'),
              changeScaleHandler: createChangeScaleHandler(['road'], 'geometry')
          });

          // Create a CustomizationControl to change the appearance of the water
          const waterControl = new CustomizationControl({
              title: 'Water',
              changeColorHandler: createChangeColorHandler(['water'], 'geometry'),
              changeOpacityHandler: createChangeOpacityHandler(['water'], 'geometry'),
              changeScaleHandler: createChangeScaleHandler(['water'], 'geometry')
          });

          // Create a CustomizationControl to change the appearance of the ground
          const groundControl = new CustomizationControl({
              title: 'Ground',
              changeColorHandler: createChangeColorHandler(['landscape', 'admin', 'land', 'transit'], 'geometry'),
              changeOpacityHandler: createChangeOpacityHandler(['landscape', 'admin', 'land', 'transit'], 'geometry')
          });

          // Create a CustomizationControl to change the appearance of the building
          const buildingControl = new CustomizationControl({
              title: 'Building',
              changeColorHandler: createChangeColorHandler(['building'], 'geometry'),
              changeOpacityHandler: createChangeOpacityHandler(['building'], 'geometry')
          });

          // Create a shared container for custom CustomizationControl's and add it to the map
          const controls = new YMapControls({position: 'top right', orientation: 'horizontal'});
          map.addChild(controls);

          // Add controls to the map
          controls
              .addChild(new YMapControl().addChild(waterControl).addChild(groundControl))
              .addChild(new YMapControl().addChild(roadControl).addChild(buildingControl));

          // A function that creates a handler for changing the color of geo objects
          function createChangeColorHandler(controlTags: string[], controlElements: VectorCustomizationElements) {
              return () => {
                  const customizationObject = customization.find(
                      (item) => typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
                  );

                  if (customizationObject) {
                      customizationObject.stylers[0]['color'] = generateColor();
                  } else {
                      const newTagObject: VectorCustomizationItem = {
                          tags: {any: controlTags},
                          elements: controlElements,
                          stylers: [{color: generateColor()}]
                      };

                      customization.push(newTagObject);
                  }
                  updateCustomization();
              };
          }

          // A function that creates a handler to change the opacity of geo objects
          function createChangeOpacityHandler(controlTags: string[], controlElements: VectorCustomizationElements) {
              return (diff: number) => {
                  const customizationObject = customization.find(
                      (item) => typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
                  );

                  if (!customizationObject) {
                      const newTagObject: VectorCustomizationItem = {
                          tags: {any: controlTags},
                          elements: controlElements,
                          stylers: [{opacity: 0.5}]
                      };
                      customization.push(newTagObject);
                  } else if (customizationObject.stylers[0]['opacity'] === undefined) {
                      customizationObject.stylers[0]['opacity'] = 0.5;
                  } else {
                      customizationObject.stylers[0]['opacity'] = +(customizationObject.stylers[0]['opacity'] + diff).toFixed(
                          1
                      );
                  }
                  updateCustomization();
              };
          }

          // A function that creates a handler to change the scale of geo objects
          function createChangeScaleHandler(controlTags: string[], controlElements: VectorCustomizationElements) {
              return (diff: number) => {
                  const customizationObject = customization.find(
                      (item) => typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
                  );

                  if (!customizationObject) {
                      const newTagObject: VectorCustomizationItem = {
                          tags: {any: controlTags},
                          elements: controlElements,
                          stylers: [{scale: 2}]
                      };
                      customization.push(newTagObject);
                  } else if (customizationObject.stylers[0]['scale'] === undefined) {
                      customizationObject.stylers[0]['scale'] = 2;
                  } else {
                      customizationObject.stylers[0]['scale'] = Math.floor(customizationObject.stylers[0]['scale'] + diff);
                  }
                  updateCustomization();
              };
          }
      }
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
  </head>
  <body>
    <div class="container">
      <div id="app"></div>
      <textarea id="code"></textarea>
    </div>
  </body>
</html>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/lib/codemirror.css" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/theme/material.css" />
    <script src="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/lib/codemirror.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/mode/javascript/javascript.js"></script>

    <script crossorigin src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

    <script crossorigin src="https://cdn.jsdelivr.net/npm/react@17/umd/react.production.min.js"></script>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/react-dom@17/umd/react-dom.production.min.js"></script>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
    <!-- To make the map appear, you must add your apikey -->
    <script src="https://api-maps.yandex.ru/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>

    <script
      data-plugins="transform-modules-umd"
      data-presets="react, typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="react, typescript" type="text/babel">
      import type {
        VectorCustomization,
        VectorCustomizationItem,
        VectorCustomizationElements
      } from '@yandex/ymaps3-types';
      import {LOCATION, initialCustomization, CustomizationControl, generateColor} from './common';

      window.map = null;

      main();
      async function main() {
        // For each object in the JS API, there is a React counterpart
        // To use the React version of the API, include the module @yandex/ymaps3-reactify
        const [ymaps3React] = await Promise.all([ymaps3.import('@yandex/ymaps3-reactify'), ymaps3.ready]);
        const reactify = ymaps3React.reactify.bindTo(React, ReactDOM);
        const {YMap, YMapDefaultSchemeLayer, YMapControls, YMapControl} = reactify.module(ymaps3);

        // Using ymaps3-rectify, we turn a custom CustomizationControl into a React component
        const {CustomizationControl: CustomizationControlR} = reactify.module({CustomizationControl});

        const {useEffect, useCallback, useMemo, useRef, useState} = React;

        // Create an editor component for manual customization of the map
        interface CodeAreaProps {
          customization: VectorCustomization;
          setCustomization: (value: VectorCustomization) => void;
        }
        const CodeArea = ({customization, setCustomization}: CodeAreaProps) => {
          const codeMirrorRef = useRef(null);
          const editorRef = useRef(null);

          const editorChangeHandler = useMemo(
            () =>
              _.debounce((_, action) => {
                // Add a check so that the event is triggered only when manually changing the customization of the map
                if (action.origin !== 'setValue') {
                  setCustomization(JSON.parse(editorRef.current.getValue()));
                }
              }, 200),
            []
          );

          useEffect(() => {
            editorRef.current = CodeMirror.fromTextArea(codeMirrorRef.current, {
              mode: 'javascript',
              theme: 'material',
              lineNumbers: true
            });

            // Set the initial size to the editor
            editorRef.current.setSize('40%', '100%');

            // Add an onchange event to the editor that will update the map customization
            editorRef.current.on('change', editorChangeHandler);

            return () => {
              editorRef.current.toTextArea(); // Clean the editor when unmounting
              editorRef.current.off('change', editorChangeHandler);
            };
          }, []);

          // Create a useEffect hook that will trigger when changing the customization of the map through the control
          useEffect(() => {
            if (JSON.stringify(customization, null, 2) !== editorRef.current.getValue()) {
              editorRef.current.setValue(JSON.stringify(customization, null, 2));
            }
          }, [customization]);

          return <textarea ref={codeMirrorRef} />;
        };

        function App() {
          const [customization, setCustomization] = useState < VectorCustomization > initialCustomization;

          // A function that creates a handler for changing the color of geo objects
          const createChangeColorHandler = useCallback(
            (controlTags: string[], controlElements: VectorCustomizationElements) => {
              return () => {
                setCustomization((customization) => {
                  const customizationObject = customization.find(
                    (item) =>
                      typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
                  );

                  if (customizationObject) {
                    customizationObject.stylers[0]['color'] = generateColor();
                  } else {
                    const newTagObject: VectorCustomizationItem = {
                      tags: {any: controlTags},
                      elements: controlElements,
                      stylers: [{color: generateColor()}]
                    };

                    customization.push(newTagObject);
                  }
                  return [...customization];
                });
              };
            },
            []
          );

          // A function that creates a handler to change the opacity of geo objects
          const createChangeOpacityHandler = useCallback(
            (controlTags: string[], controlElements: VectorCustomizationElements) => {
              return (diff: number) => {
                setCustomization((customization) => {
                  const customizationObject = customization.find(
                    (item) =>
                      typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
                  );

                  if (!customizationObject) {
                    const newTagObject: VectorCustomizationItem = {
                      tags: {any: controlTags},
                      elements: controlElements,
                      stylers: [{opacity: 0.5}]
                    };
                    customization.push(newTagObject);
                  } else if (customizationObject.stylers[0]['opacity'] === undefined) {
                    customizationObject.stylers[0]['opacity'] = 0.5;
                  } else {
                    customizationObject.stylers[0]['opacity'] = +(
                      customizationObject.stylers[0]['opacity'] + diff
                    ).toFixed(1);
                  }

                  return [...customization];
                });
              };
            },
            []
          );

          // A function that creates a handler to change the scale of geo objects
          const createChangeScaleHandler = useCallback(
            (controlTags: string[], controlElements: VectorCustomizationElements) => {
              return (diff: number) => {
                setCustomization((customization) => {
                  const customizationObject = customization.find(
                    (item) =>
                      typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
                  );

                  if (!customizationObject) {
                    const newTagObject: VectorCustomizationItem = {
                      tags: {any: controlTags},
                      elements: controlElements,
                      stylers: [{scale: 2}]
                    };
                    customization.push(newTagObject);
                  } else if (customizationObject.stylers[0]['scale'] === undefined) {
                    customizationObject.stylers[0]['scale'] = 2;
                  } else {
                    customizationObject.stylers[0]['scale'] = Math.floor(
                      customizationObject.stylers[0]['scale'] + diff
                    );
                  }

                  return [...customization];
                });
              };
            },
            []
          );

          return (
            <div className={'container'}>
              <YMap location={LOCATION} showScaleInCopyrights={true} ref={(x) => (map = x)}>
                {/* Add a map scheme layer with a custom customization props */}
                <YMapDefaultSchemeLayer customization={customization} />

                {/* Add a shared container to the map for custom CustomizationControl's */}
                <YMapControls position={'top right'} orientation={'horizontal'}>
                  <YMapControl>
                    {/* Add a CustomizationControl to the map to change the appearance of the water */}
                    <CustomizationControlR
                      title="Water"
                      changeColorHandler={createChangeColorHandler(['water'], 'geometry')}
                      changeOpacityHandler={createChangeOpacityHandler(['water'], 'geometry')}
                      changeScaleHandler={createChangeScaleHandler(['water'], 'geometry')}
                    />
                    {/* Add a CustomizationControl to the map to change the appearance of the ground */}
                    <CustomizationControlR
                      title="Ground"
                      changeColorHandler={createChangeColorHandler(
                        ['landscape', 'admin', 'land', 'transit'],
                        'geometry'
                      )}
                      changeOpacityHandler={createChangeOpacityHandler(
                        ['landscape', 'admin', 'land', 'transit'],
                        'geometry'
                      )}
                    />
                  </YMapControl>
                  <YMapControl>
                    {/* Add a CustomizationControl to the map to change the appearance of the road */}
                    <CustomizationControlR
                      title="Road"
                      changeColorHandler={createChangeColorHandler(['road'], 'geometry')}
                      changeOpacityHandler={createChangeOpacityHandler(['road'], 'geometry')}
                      changeScaleHandler={createChangeScaleHandler(['road'], 'geometry')}
                    />
                    {/* Add a CustomizationControl to the map to change the appearance of the building */}
                    <CustomizationControlR
                      title="Building"
                      changeColorHandler={createChangeColorHandler(['building'], 'geometry')}
                      changeOpacityHandler={createChangeOpacityHandler(['building'], 'geometry')}
                    />
                  </YMapControl>
                </YMapControls>
              </YMap>

              <CodeArea customization={customization} setCustomization={setCustomization} />
            </div>
          );
        }

        ReactDOM.render(
          <React.StrictMode>
            <App />
          </React.StrictMode>,
          document.getElementById('app')
        );
      }
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/lib/codemirror.css" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/theme/material.css" />
    <script src="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/lib/codemirror.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/mode/javascript/javascript.js"></script>

    <script crossorigin src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

    <script crossorigin src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>

    <!-- To make the map appear, you must add your apikey -->
    <script src="https://api-maps.yandex.ru/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>

    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
      import {LOCATION, initialCustomization, CustomizationControl, generateColor} from './common';
      import type {VectorCustomization} from '@yandex/ymaps3-types';

      window.map = null;

      async function main() {
        // For each object in the JS API, there is a Vue counterpart
        // To use the Vue version of the API, include the module @yandex/ymaps3-vuefy
        const [ymaps3Vue] = await Promise.all([ymaps3.import('@yandex/ymaps3-vuefy'), ymaps3.ready]);
        const vuefy = ymaps3Vue.vuefy.bindTo(Vue);
        const {YMap, YMapDefaultSchemeLayer, YMapControls, YMapControl} = vuefy.module(ymaps3);

        // Using ymaps3-vuefy, we turn a custom CustomizationControl into a Vue component
        const {CustomizationControl: CustomizationControlV} = vuefy.module({CustomizationControl});

        //const customization = Vue.ref(initialCustomization);
        const customization = Vue.shallowRef(initialCustomization);

        // A function that creates a handler for changing the color of geo objects
        const createChangeColorHandler = (controlTags, controlElements) => {
          return () => {
            if (!customization.value.some((item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags))) {
              const newTagObject = {
                tags: {any: controlTags},
                elements: controlElements,
                stylers: [{color: generateColor()}]
              };
              customization.value.push(newTagObject);
            } else {
              const customizationObject = customization.value.find(
                (item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
              );
              customizationObject.stylers[0]['color'] = generateColor();
            }
            customization.value = [...customization.value];
          };
        };

        // A function that creates a handler to change the opacity of geo objects
        const createChangeOpacityHandler = (controlTags, controlElements) => {
          return (diff) => {
            if (!customization.value.some((item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags))) {
              const newTagObject = {
                tags: {any: controlTags},
                elements: controlElements,
                stylers: [{opacity: 0.5}]
              };
              customization.value.push(newTagObject);
            } else {
              const customizationObject = customization.value.find(
                (item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
              );
              if (customizationObject.stylers[0]['opacity'] === undefined) {
                customizationObject.stylers[0]['opacity'] = 0.5;
              } else {
                customizationObject.stylers[0]['opacity'] = +(
                  customizationObject.stylers[0]['opacity'] + diff
                ).toFixed(1);
              }
            }
            customization.value = [...customization.value];
          };
        };

        // A function that creates a handler to change the scale of geo objects
        const createChangeScaleHandler = (controlTags, controlElements) => {
          return (diff) => {
            if (!customization.value.some((item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags))) {
              const newTagObject = {
                tags: {any: controlTags},
                elements: controlElements,
                stylers: [{scale: 2}]
              };
              customization.value.push(newTagObject);
            } else {
              const customizationObject = customization.value.find(
                (item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
              );
              if (customizationObject.stylers[0]['scale'] === undefined) {
                customizationObject.stylers[0]['scale'] = 2;
              } else {
                customizationObject.stylers[0]['scale'] = Math.floor(customizationObject.stylers[0]['scale'] + diff);
              }
            }
            customization.value = [...customization.value];
          };
        };

        const CodeArea = Vue.defineComponent({
          props: ['customization'],
          emits: ['change'],
          setup(props, {emit}) {
            const codeMirrorRef = (refHtmlTextareaElement: HTMLTextAreaElement) => {
              if (editorRef.value) return;
              editorRef.value = CodeMirror.fromTextArea(refHtmlTextareaElement, {
                mode: 'javascript',
                theme: 'material',
                lineNumbers: true
              });

              editorRef.value.setSize('40%', '100%');
              editorRef.value.on('change', editorChangeHandler);
              Vue.nextTick().then(() => {
                editorRef.value.setValue(JSON.stringify(props.customization, null, 2));
              });
            };
            const editorRef = Vue.ref(null);
            const editorChangeHandler = (_, action) => {
              // Add a check so that the event is triggered only when manually changing the customization of the map
              if (action.origin !== 'setValue') {
                emit('change', JSON.parse(editorRef.value.getValue()));
              }
            };

            Vue.onBeforeUnmount(() => {
              if (editorRef.value) {
                editorRef.value.toTextArea();
                editorRef.value.off('change', editorChangeHandler);
              }
            });

            Vue.watch(
              () => props.customization,
              (newCustomization) => {
                if (JSON.stringify(newCustomization, null, 2) !== editorRef.value.getValue()) {
                  editorRef.value.setValue(JSON.stringify(newCustomization, null, 2));
                }
              },
              {deep: true}
            );

            return {
              codeMirrorRef,
              editorRef,
              editorChangeHandler
            };
          },
          template: `<textarea :ref="codeMirrorRef"/>`
        });

        const app = Vue.createApp({
          setup() {
            const refMap = (ref) => {
              window.map = ref?.entity;
            };

            const onChange = (newCustomization) => {
              customization.value = newCustomization;
            };

            return {
              LOCATION,
              refMap,
              customization,
              createChangeColorHandler,
              createChangeOpacityHandler,
              createChangeScaleHandler,
              onChange
            };
          },
          components: {
            YMap,
            YMapDefaultSchemeLayer,
            YMapControls,
            YMapControl,
            CustomizationControlV,
            CodeArea
          },
          template: `
          <div class="container">
            <!-- Initialize the map and pass initialization parameters -->
            <YMap :location="LOCATION" :showScaleInCopyrights="true" :ref="refMap">
              <!-- Add a map scheme layer with a custom customization props -->
              <YMapDefaultSchemeLayer :customization="customization"/>
              <!-- Add a shared container to the map for custom CustomizationControl's -->
              <YMapControls position="top right" orientation="horizontal">
                <YMapControl>
                  <!-- Add a CustomizationControl to the map to change the appearance of the water -->
                  <CustomizationControlV
                      title="Water"
                      :changeColorHandler="createChangeColorHandler(['water'], 'geometry')"
                      :changeOpacityHandler="createChangeOpacityHandler(['water'], 'geometry')"
                      :changeScaleHandler="createChangeScaleHandler(['water'], 'geometry')"
                  />
                  <!-- Add a CustomizationControl to the map to change the appearance of the ground -->
                  <CustomizationControlV
                      title="Ground"
                      :changeColorHandler="createChangeColorHandler(['landscape', 'admin', 'land', 'transit'], 'geometry')"
                      :changeOpacityHandler="createChangeOpacityHandler(['landscape', 'admin', 'land', 'transit'], 'geometry')"
                  />
                </YMapControl>
                <YMapControl>
                  <!-- Add a CustomizationControl to the map to change the appearance of the road -->
                  <CustomizationControlV
                      title="Road"
                      :changeColorHandler="createChangeColorHandler(['road'], 'geometry')"
                      :changeOpacityHandler="createChangeOpacityHandler(['road'], 'geometry')"
                      :changeScaleHandler="createChangeScaleHandler(['road'], 'geometry')"
                  />
                  <!-- Add a CustomizationControl to the map to change the appearance of the building -->
                  <CustomizationControlV
                      title="Building"
                      :changeColorHandler="createChangeColorHandler(['building'], 'geometry')"
                      :changeOpacityHandler="createChangeOpacityHandler(['building'], 'geometry')"
                  />
                </YMapControl>
              </YMapControls>
            </YMap>

            <CodeArea :customization="customization" @change="onChange"/>
          </div>
        `
        });

        app.mount('#app');
      }

      main();
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
.container {
  display: flex;
  height: 100%;
}

.customizationControl {
  padding: 10px 0;
  margin: 0 15px;

  display: flex;
  flex-direction: column;
  gap: 15px;
}

.customizationControl:first-child {
  border-bottom: 1px solid #c0c0c1;
}

.customizationControl__title {
  text-align: center;

  font-size: 18px;
  font-weight: 600;
}

.customizationControl__section {
  display: flex;
  align-items: center;
  gap: 5px;
}

.customizationControl__sectionTitle {
  flex-basis: 50%;
}

.customizationControl__btn {
  border: none;
  cursor: pointer;

  min-width: 30px;
  padding: 8px;

  color: rgb(255, 255, 255);
  border-radius: 8px;
  background-color: rgba(0, 122, 252, 0.9);
  transition: background-color 0.2s;
}

.customizationControl__btn:hover {
  background-color: rgb(0, 110, 252);
}

.customizationControl__btn:active {
  background-color: rgb(0, 122, 252);
}
import type codemirrorTypes from 'codemirror';
import type lodashTypes from 'lodash';
import type {VectorCustomization, YMapLocationRequest} from '@yandex/ymaps3-types';

declare global {
  const CodeMirror: typeof codemirrorTypes;
  const _: typeof lodashTypes;
}

export const LOCATION: YMapLocationRequest = {
  center: [37.623082, 55.75254], // starting position [lng, lat]
  zoom: 11 // starting zoom
};

// Starting parameters of map customization
export const initialCustomization: VectorCustomization = [
  {
    tags: {
      any: ['road']
    },
    elements: 'geometry',
    stylers: [
      {
        color: '#4E4E4E'
      }
    ]
  },
  {
    tags: {
      any: ['water']
    },
    elements: 'geometry',
    stylers: [
      {
        color: '#000000'
      }
    ]
  },
  {
    tags: {
      any: ['landscape', 'admin', 'land', 'transit']
    },
    elements: 'geometry',
    stylers: [
      {
        color: '#212121'
      }
    ]
  },
  {
    tags: {
      any: ['building']
    },
    elements: 'geometry',
    stylers: [
      {
        color: '#757474'
      }
    ]
  }
];

// Function generates a random color in HEX format
export const generateColor = () => {
  return '#' + Math.floor(Math.random() * 16777215).toString(16);
};

// Create a custom control to change the customization of the map
export let CustomizationControl = null;

interface CustomizationControlProps {
  title: string;
  changeColorHandler?: () => void;
  changeOpacityHandler?: (diff: number) => void;
  changeScaleHandler?: (diff: number) => void;
}

// Wait for the api to load to access the entity system (YMapComplexEntity)
ymaps3.ready.then(() => {
  class CustomizationControlClass extends ymaps3.YMapComplexEntity<CustomizationControlProps> {
    private _element: HTMLDivElement;
    private _detachDom: () => void;
    constructor(props: CustomizationControlProps) {
      super(props);
      this._element = this._createElement(props);
    }

    // Creates a control's DOM element based on the passed properties
    _createElement(props: CustomizationControlProps) {
      const {title, changeColorHandler, changeOpacityHandler, changeScaleHandler} = props;

      const customizationElement = document.createElement('div');
      customizationElement.className = 'customizationControl';

      const myControlTitle = document.createElement('div');
      myControlTitle.className = 'customizationControl__title';
      myControlTitle.textContent = title;

      customizationElement.appendChild(myControlTitle);

      if (changeColorHandler) {
        const colorSection = this._createControlSection('Color:', ['random'], [changeColorHandler]);
        customizationElement.appendChild(colorSection);
      }

      if (changeOpacityHandler) {
        const opacitySection = this._createControlSection(
          'Opacity:',
          ['-', '+'],
          [() => changeOpacityHandler(-0.1), () => changeOpacityHandler(0.1)]
        );
        customizationElement.appendChild(opacitySection);
      }

      if (changeScaleHandler) {
        const scaleSection = this._createControlSection(
          'Scale:',
          ['-', '+'],
          [() => changeScaleHandler(-1), () => changeScaleHandler(1)]
        );
        customizationElement.appendChild(scaleSection);
      }

      return customizationElement;
    }

    _createControlSection(title: string, buttonLabels: string[], onClickHandler: EventListener[]) {
      const section = document.createElement('div');
      section.className = 'customizationControl__section';

      const sectionTitle = document.createElement('div');
      sectionTitle.className = 'customizationControl__sectionTitle';
      sectionTitle.textContent = title;
      section.appendChild(sectionTitle);

      for (let i = 0; i < buttonLabels.length; i++) {
        const sectionButton = document.createElement('button');
        sectionButton.className = 'customizationControl__btn';
        sectionButton.textContent = buttonLabels[i];
        sectionButton.addEventListener('click', onClickHandler[i]);
        section.appendChild(sectionButton);
      }

      return section;
    }

    // Handler for attaching the control to the map
    _onAttach() {
      this._detachDom = ymaps3.useDomContext(this, this._element, this._element);
    }

    // Handler for detaching control from the map
    _onDetach() {
      this._detachDom();
      this._detachDom = null;
      this._element = null;
    }
  }

  CustomizationControl = CustomizationControlClass;
});